UI: text wraps inside its box everywhere (global wrapping, shrinkable flex items, wrapping buttons, unknown icon fallback)
This commit is contained in:
8 files changed
+54
-9
No files matched your search
+14
-1
@@ -40,10 +40,17 @@ html, body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
/* Long names and unbroken strings wrap inside their box instead of spilling out of it. */
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
#app { height: 100%; }
|
||||
|
||||
/* Flex and grid items may shrink below their content, so long names wrap or truncate inside their box instead of pushing it wider. */
|
||||
*, *::before, *::after { min-width: 0; }
|
||||
svg, img, canvas, video, input[type='checkbox'], input[type='radio'] { flex-shrink: 0; }
|
||||
|
||||
|
||||
h1, h2, h3, h4 { margin: 0; letter-spacing: -0.015em; }
|
||||
p { margin: 0; }
|
||||
a { color: var(--accent); text-decoration: none; }
|
||||
@@ -85,8 +92,11 @@ button {
|
||||
font-weight: 520;
|
||||
font-size: 0.9rem;
|
||||
transition: background 0.15s, border-color 0.15s, opacity 0.15s;
|
||||
white-space: nowrap;
|
||||
/* Labels may wrap when space runs out; compact buttons below stay on one line. */
|
||||
white-space: normal;
|
||||
max-width: 100%;
|
||||
}
|
||||
button.primary, button.ghost, button.danger, button.icon, button.tool { white-space: nowrap; }
|
||||
button:hover { background: color-mix(in srgb, var(--text) 11%, transparent); }
|
||||
button:active { background: color-mix(in srgb, var(--text) 14%, transparent); }
|
||||
button:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }
|
||||
@@ -154,3 +164,6 @@ label.field { display: flex; flex-direction: column; gap: 0.45rem; font-size: 0.
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
@keyframes fade-up { from { opacity: 0; transform: translateY(0.6rem); } to { opacity: 1; transform: none; } }
|
||||
@keyframes fade { from { opacity: 0; } }
|
||||
|
||||
code, pre, kbd, samp { overflow-wrap: anywhere; word-break: break-word; }
|
||||
pre { white-space: pre-wrap; }
|
||||
@@ -15,7 +15,7 @@
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.plink { all: unset; cursor: pointer; font: inherit; color: inherit; border-radius: 4px; transition: color 0.12s ease; }
|
||||
.plink { all: unset; cursor: pointer; font: inherit; color: inherit; border-radius: 4px; transition: color 0.12s ease; display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: inherit; vertical-align: bottom; }
|
||||
.plink:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
|
||||
.plink:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||
</style>
|
||||
@@ -86,7 +86,7 @@
|
||||
.inst.active { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--text); border-color: color-mix(in srgb, var(--accent) 55%, transparent); box-shadow: 0 0 16px color-mix(in srgb, var(--accent) 30%, transparent); }
|
||||
.inst.active::before { content: ''; position: absolute; left: -0.75rem; top: 0.65rem; bottom: 0.65rem; width: 3.5px; border-radius: 0 3px 3px 0; background: var(--accent); box-shadow: 0 0 8px var(--accent); }
|
||||
.inst.running::after { content: ''; position: absolute; right: -0.2rem; bottom: -0.2rem; width: 0.8rem; height: 0.8rem; border-radius: 50%; background: var(--success); border: 2px solid var(--bg); }
|
||||
.tip { position: fixed; left: 5.2rem; transform: translateY(0); font-style: normal; font-size: 0.82rem; font-weight: 600; white-space: nowrap; padding: 0.4rem 0.75rem; border-radius: 0.5rem; background: var(--surface); border: 1px solid var(--line-strong); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); opacity: 0; pointer-events: none; transition: opacity 0.15s; z-index: 50; color: var(--text); }
|
||||
.tip { position: fixed; left: 5.2rem; transform: translateY(0); font-style: normal; font-size: 0.82rem; font-weight: 600; max-width: 16rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 0.4rem 0.75rem; border-radius: 0.5rem; background: var(--surface); border: 1px solid var(--line-strong); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); opacity: 0; pointer-events: none; transition: opacity 0.15s; z-index: 50; color: var(--text); }
|
||||
.inst:hover .tip { opacity: 1; }
|
||||
.bottom { display: flex; flex-direction: column; align-items: center; gap: 0.4rem; }
|
||||
.big { width: 2.6rem; height: 2.6rem; position: relative; border-radius: var(--radius-sm); transition: transform 0.12s; }
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
.ph.on { background: color-mix(in srgb, var(--accent) 30%, transparent); }
|
||||
.info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.05rem; }
|
||||
.name { gap: 0.5rem; }
|
||||
.name strong { font-size: 0.85rem; font-weight: 560; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.name strong { min-width: 0; flex: 0 1 auto; font-size: 0.85rem; font-weight: 560; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.state { font-size: 0.72rem; font-weight: 520; color: var(--muted); display: inline-flex; align-items: center; gap: 0.3rem; }
|
||||
.state::before { content: ''; width: 0.45rem; height: 0.45rem; border-radius: 50%; background: currentColor; }
|
||||
.state.online { color: var(--success); }
|
||||
|
||||
@@ -133,7 +133,22 @@ const wallet = {
|
||||
};
|
||||
const mockWallet = () => JSON.parse(JSON.stringify(wallet));
|
||||
|
||||
// `?stress` in the dev URL makes every name, title and description much longer, to check that no screen lets text spill out of its box.
|
||||
const STRESS = typeof location !== 'undefined' && new URLSearchParams(location.search).has('stress');
|
||||
const TEXT_KEYS = /(^|_)(name|title|username|description|motd|message|desc|label|bio|tagline|text|rank|guild)(_|$)/i;
|
||||
function stress(value: unknown, key = ''): unknown {
|
||||
if (Array.isArray(value)) return value.map((v) => stress(v, key));
|
||||
if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, stress(v, k)]));
|
||||
if (typeof value === 'string' && TEXT_KEYS.test(key) && /^[A-Za-z][A-Za-z '&:,.\-]{2,60}$/.test(value)) return `${value} With An Extraordinarily Long Unbroken Continuation`;
|
||||
return value;
|
||||
}
|
||||
|
||||
export async function mockInvoke(cmd: string, args: Record<string, any>): Promise<unknown> {
|
||||
const result = await mockInvokeInner(cmd, args);
|
||||
return STRESS ? stress(result) : result;
|
||||
}
|
||||
|
||||
async function mockInvokeInner(cmd: string, args: Record<string, any>): Promise<unknown> {
|
||||
await sleep(40);
|
||||
switch (cmd) {
|
||||
case 'get_public_servers':
|
||||
|
||||
Reference in new issue
Block a user