mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-24 05:53:32 +00:00
fix(ui): standardize safe text metrics (#44106)
This commit is contained in:
parent
a82318019f
commit
ad59ccc2e2
28 changed files with 86 additions and 58 deletions
|
|
@ -19,6 +19,12 @@
|
|||
|
||||
- Always prefer `createStore` over multiple `createSignal` calls
|
||||
|
||||
## Typography
|
||||
|
||||
- Use `--line-height-compact` (`16px`) for `13px` compact UI text and `--line-height-base` (`20px`) for body text.
|
||||
- Do not use `leading-none`, `line-height: 1`, or a `13px` line height for normal text. Inter descenders clip inside truncation and overflow containers.
|
||||
- Keep control and row heights explicit. Fix font metrics directly rather than using transforms, negative margins, or clip-padding compensation.
|
||||
|
||||
## Localization
|
||||
|
||||
- NEVER hardcode user-visible English strings in production code. ALWAYS use an i18n key for visible copy, placeholders, accessible labels, tooltips, menus, dialogs, toasts, empty states, and displayed errors.
|
||||
|
|
|
|||
|
|
@ -86,10 +86,12 @@ test("shows a delegating row while subagent input streams", async ({ page }) =>
|
|||
|
||||
const delegating = page.locator('[data-component="task-tool-delegating"]')
|
||||
await expect(delegating).toBeVisible()
|
||||
await expect(delegating.locator('[data-component="text-shimmer"]')).toHaveAttribute(
|
||||
const shimmer = delegating.locator('[data-component="text-shimmer"]')
|
||||
await expect(shimmer).toHaveAttribute(
|
||||
"aria-label",
|
||||
"Delegating agent...",
|
||||
)
|
||||
await expect(shimmer).toHaveCSS("line-height", "16px")
|
||||
const icon = delegating.locator('[data-slot="icon-svg"]')
|
||||
await expect(icon.locator('use[href="#opencode-v2-icon-subagent"]')).toBeVisible()
|
||||
await expect(icon).toHaveCSS("color", "rgb(174, 174, 174)")
|
||||
|
|
@ -127,11 +129,11 @@ test("renders the moved location notice in its compact timeline style", async ({
|
|||
await expect(notice).toHaveCSS("padding-bottom", "4px")
|
||||
await expect(label).toHaveCSS("font-size", "13px")
|
||||
await expect(label).toHaveCSS("font-weight", "530")
|
||||
await expect(label).toHaveCSS("line-height", "13px")
|
||||
await expect(label).toHaveCSS("line-height", "16px")
|
||||
await expect(label).toHaveCSS("color", "rgb(128, 128, 128)")
|
||||
await expect(value).toHaveCSS("font-size", "13px")
|
||||
await expect(value).toHaveCSS("font-weight", "440")
|
||||
await expect(value).toHaveCSS("line-height", "13px")
|
||||
await expect(value).toHaveCSS("line-height", "16px")
|
||||
await expect(value).toHaveCSS("color", "rgb(128, 128, 128)")
|
||||
await expect(value).toHaveCSS("text-overflow", "ellipsis")
|
||||
await expect(value).toHaveCSS("white-space", "nowrap")
|
||||
|
|
|
|||
|
|
@ -121,19 +121,20 @@ test("labels skill tools from IDs and result metadata", async ({ page }) => {
|
|||
messages: [
|
||||
userMessage(),
|
||||
assistantMessage([
|
||||
toolPart(pending, "skill", "running", { id: "sample-skill" }),
|
||||
toolPart(pending, "skill", "running", { id: "frontend-design" }),
|
||||
toolPart(completed, "skill", "completed", { id: "opencode" }, { metadata: { name: "OpenCode" } }),
|
||||
]),
|
||||
],
|
||||
})
|
||||
|
||||
for (const [id, name] of [
|
||||
[pending, "sample-skill"],
|
||||
[pending, "frontend-design"],
|
||||
[completed, "OpenCode"],
|
||||
] as const) {
|
||||
const skill = page.locator(`[data-timeline-part-id="${id}"]`)
|
||||
const loaded = skill.locator('[data-component="tool-loaded-item"]')
|
||||
await expect(loaded).toHaveAttribute("aria-label", `Loaded ${name} skill`)
|
||||
await expect(loaded).toHaveCSS("line-height", "16px")
|
||||
await expect(loaded.locator('[data-slot="tool-loaded-label"]')).toHaveText("Loaded")
|
||||
await expect(loaded.locator('[data-slot="tool-loaded-kind"]')).toHaveText("skill")
|
||||
await expect(loaded.locator('[data-component="text-shimmer"]')).toHaveAttribute("aria-label", name)
|
||||
|
|
|
|||
|
|
@ -509,7 +509,7 @@ function HomeSessionsEmpty(props: { onNewSession?: () => void; language: ReturnT
|
|||
<div class="flex min-h-full flex-col items-center gap-4 px-6 pt-[52px] text-center">
|
||||
<div
|
||||
class={`
|
||||
shrink-0 text-[13px] leading-[13px] tracking-[-0.04px]
|
||||
shrink-0 text-[13px] leading-text-compact tracking-[-0.04px]
|
||||
text-v2-text-text-base [font-weight:530]
|
||||
`}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ function ProviderTip() {
|
|||
>
|
||||
<button
|
||||
type="button"
|
||||
class="flex h-6 min-w-0 items-center rounded-[4px] pl-1.5 text-[13px] leading-none tracking-[-0.04px] text-v2-text-text-faint transition-[background-color,color] duration-150 ease-in-out hover:bg-v2-overlay-simple-overlay-hover hover:text-v2-text-text-muted focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:text-v2-text-text-muted focus-visible:outline-none"
|
||||
class="flex h-6 min-w-0 items-center rounded-[4px] pl-1.5 text-[13px] leading-text-compact tracking-[-0.04px] text-v2-text-text-faint transition-[background-color,color] duration-150 ease-in-out hover:bg-v2-overlay-simple-overlay-hover hover:text-v2-text-text-muted focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:text-v2-text-text-muted focus-visible:outline-none"
|
||||
onClick={openProviders}
|
||||
>
|
||||
<span class="truncate">{language.t("home.providerTip")}</span>
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ function ProviderPicker(props: { directory?: string; onSelect: (provider: string
|
|||
{(group) => (
|
||||
<Show when={group.items().length > 0}>
|
||||
<section class="flex flex-col">
|
||||
<div class="px-3 pb-2 text-[13px] font-[440] leading-none tracking-[-0.04px] text-v2-text-text-muted">
|
||||
<div class="px-3 pb-2 text-[13px] font-[440] leading-text-compact tracking-[-0.04px] text-v2-text-text-muted">
|
||||
{group.title}
|
||||
</div>
|
||||
<For each={group.items()}>
|
||||
|
|
@ -198,7 +198,7 @@ function ProviderPicker(props: { directory?: string; onSelect: (provider: string
|
|||
<button
|
||||
type="button"
|
||||
data-provider-id={provider.id}
|
||||
class="flex min-h-9 w-full items-center gap-2 rounded-md px-3 py-2.5 text-left text-[13px] leading-none tracking-[-0.04px] hover:bg-v2-overlay-simple-overlay-hover focus:bg-v2-overlay-simple-overlay-hover focus:outline-none"
|
||||
class="flex min-h-9 w-full items-center gap-2 rounded-md px-3 py-2.5 text-left text-[13px] leading-text-compact tracking-[-0.04px] hover:bg-v2-overlay-simple-overlay-hover focus:bg-v2-overlay-simple-overlay-hover focus:outline-none"
|
||||
classList={{ "bg-v2-overlay-simple-overlay-hover": store.active === provider.id }}
|
||||
onMouseEnter={() => setStore("active", provider.id)}
|
||||
disabled={store.connecting !== undefined}
|
||||
|
|
|
|||
|
|
@ -386,7 +386,7 @@
|
|||
padding: 48px 24px;
|
||||
font-size: 13px;
|
||||
font-weight: 440;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
color: var(--v2-text-text-muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ export function BackgroundWorkSummary(props: { tasks: BackgroundTask[] }) {
|
|||
{(task) => (
|
||||
<div
|
||||
data-component="session-background-list-item"
|
||||
class="flex h-7 min-w-0 items-center gap-2 rounded-[4px] px-3 text-[13px] font-[440] leading-none tracking-[-0.04px]"
|
||||
class="flex h-7 min-w-0 items-center gap-2 rounded-[4px] px-3 text-[13px] font-[440] leading-text-compact tracking-[-0.04px]"
|
||||
>
|
||||
<span class="shrink-0 text-v2-text-text-base">{taskType(task)}</span>
|
||||
<span class="min-w-0 flex-1 truncate text-v2-text-text-faint">{task.label}</span>
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@
|
|||
font-style: normal;
|
||||
font-size: 13px;
|
||||
font-weight: 530;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--v2-text-text-base);
|
||||
font-variation-settings: "slnt" 0;
|
||||
|
|
@ -281,7 +281,7 @@
|
|||
padding-block: 20px;
|
||||
font-size: 13px;
|
||||
font-weight: 440;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
color: var(--v2-text-text-muted);
|
||||
}
|
||||
|
||||
|
|
@ -289,7 +289,7 @@
|
|||
padding-inline-end: 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 440;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
color: var(--v2-text-text-muted);
|
||||
opacity: 0;
|
||||
transition: opacity 200ms ease;
|
||||
|
|
@ -307,7 +307,7 @@
|
|||
background: transparent;
|
||||
font-size: 13px;
|
||||
font-weight: 530;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
color: var(--v2-text-text-accent);
|
||||
cursor: pointer;
|
||||
text-align: start;
|
||||
|
|
@ -329,7 +329,7 @@
|
|||
padding-bottom: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 530;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
}
|
||||
|
||||
.settings-providers .settings-section-title + [data-component="settings-list"] {
|
||||
|
|
@ -478,7 +478,7 @@
|
|||
padding-block: 48px;
|
||||
font-size: 13px;
|
||||
font-weight: 440;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
color: var(--v2-text-text-muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -495,7 +495,7 @@
|
|||
padding-bottom: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 530;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
}
|
||||
|
||||
.settings-shortcuts [data-component="settings-list"] {
|
||||
|
|
@ -525,7 +525,7 @@
|
|||
.settings-shortcuts [data-component="settings-list"] > div > span {
|
||||
font-weight: 440;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--v2-text-text-base);
|
||||
font-variation-settings: "slnt" 0;
|
||||
|
|
@ -577,7 +577,7 @@
|
|||
padding-block: 48px;
|
||||
font-size: 13px;
|
||||
font-weight: 440;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
color: var(--v2-text-text-muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -654,7 +654,7 @@
|
|||
.settings-servers-name {
|
||||
font-size: 13px;
|
||||
font-weight: 530;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
color: var(--v2-text-text-base);
|
||||
}
|
||||
|
||||
|
|
@ -674,7 +674,7 @@
|
|||
padding-block: 48px;
|
||||
font-size: 13px;
|
||||
font-weight: 440;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
color: var(--v2-text-text-muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -784,7 +784,7 @@
|
|||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
font-weight: 530;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
letter-spacing: -0.04px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
|
@ -796,7 +796,7 @@
|
|||
.settings-workspaces-meta {
|
||||
font-size: 13px;
|
||||
font-weight: 440;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
color: var(--v2-text-text-faint);
|
||||
}
|
||||
|
||||
|
|
@ -856,7 +856,7 @@
|
|||
padding-block: 48px;
|
||||
font-size: 13px;
|
||||
font-weight: 440;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
color: var(--v2-text-text-muted);
|
||||
}
|
||||
|
||||
|
|
@ -933,7 +933,7 @@
|
|||
.settings-server-dialog-label {
|
||||
font-size: 13px;
|
||||
font-weight: 530;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
color: var(--v2-text-text-base);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@
|
|||
color: var(--v2-text-text-base);
|
||||
font-size: 13px;
|
||||
font-weight: 530;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
}
|
||||
|
||||
.project-settings-extension-section-header > :last-child {
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ function ProjectSettingsDialog(props: { project: LocalProject; server: ServerCon
|
|||
</Field>
|
||||
|
||||
<div class="flex w-full flex-col gap-2">
|
||||
<div class="select-none text-[13px] font-[530] leading-none tracking-[-0.04px] text-v2-text-text-base">
|
||||
<div class="select-none text-[13px] font-[530] leading-text-compact tracking-[-0.04px] text-v2-text-text-base">
|
||||
{language.t("dialog.project.edit.icon")}
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
|
|
@ -150,7 +150,7 @@ function ProjectSettingsDialog(props: { project: LocalProject; server: ServerCon
|
|||
|
||||
<Show when={!model.store.iconOverride}>
|
||||
<div class="flex w-full flex-col gap-2">
|
||||
<div class="select-none text-[13px] font-[530] leading-none tracking-[-0.04px] text-v2-text-text-base">
|
||||
<div class="select-none text-[13px] font-[530] leading-text-compact tracking-[-0.04px] text-v2-text-text-base">
|
||||
{language.t("dialog.project.edit.color")}
|
||||
</div>
|
||||
<div class="-ml-1 flex gap-1.5">
|
||||
|
|
|
|||
|
|
@ -88,9 +88,9 @@ function Cell(props: {
|
|||
</div>
|
||||
<div
|
||||
classList={{
|
||||
"uppercase leading-none font-bold tabular-nums": true,
|
||||
"text-[11px]": !!props.inline,
|
||||
"text-[13px] sm:text-[14px]": !props.inline,
|
||||
"uppercase font-bold tabular-nums": true,
|
||||
"text-[11px] leading-text-tight": !!props.inline,
|
||||
"text-[13px] leading-text-compact sm:text-[14px]": !props.inline,
|
||||
"text-text-on-critical-base": !!props.bad,
|
||||
"opacity-70": !!props.dim,
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@
|
|||
[data-slot="server"] {
|
||||
font-weight: 440;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--v2-text-text-muted);
|
||||
overflow: hidden;
|
||||
|
|
|
|||
|
|
@ -7,3 +7,10 @@
|
|||
- NEVER change existing English text or English keys to facilitate translation. English is intentional, designer-written source copy; adapt locale-specific translations and i18n mechanics around it.
|
||||
- Do not translate from model knowledge alone. Verify terminology and grammar with Unicode CLDR locale/plural data, Microsoft Localization Style Guides and terminology, Apple localization/style guidance and localized platform UI, Mozilla localization style guides, Mozilla Pontoon, and the Firefox localization corpus at `github.com/mozilla-l10n/firefox-l10n`.
|
||||
- Also use the relevant language authority or official dictionary for the locale (for example RAE/Fundéu, FranceTerme, Duden, TDK, Kotus/Kielitoimiston sanakirja, Språkrådet/Bokmålsordboka, Rada Języka Polskiego/PWN, the Russian and Arabic language academies, the Ukrainian Orthography, Taiwan MOE dictionaries, or the Royal Society of Thailand). Treat the English dictionary as the semantic source of truth and preserve placeholders, code identifiers, product names, and keyboard labels.
|
||||
|
||||
## Typography
|
||||
|
||||
- Use `--line-height-compact` (`16px`) for `13px` transcript, tool, notice, and truncation text. Use `--line-height-base` (`20px`) for body text.
|
||||
- Never copy Figma's generated `leading-none` onto text. Inter descenders clip when a solid `13px` line box meets `overflow: hidden`, `overflow: clip`, or truncation.
|
||||
- Keep fixed row dimensions explicit; correct inner line metrics do not require transforms, negative margins, or paint-space compensation.
|
||||
- `TextShimmer` inherits font metrics, so put typography overrides on its parent.
|
||||
|
|
|
|||
|
|
@ -182,7 +182,8 @@
|
|||
font-family: var(--v2-font-family-sans);
|
||||
font-size: 13px;
|
||||
font-weight: 530;
|
||||
line-height: var(--v2-line-height-compact, 16px);
|
||||
/* Keep compact text on the shared metric; solid 13px line boxes clip Inter descenders. */
|
||||
line-height: var(--line-height-compact);
|
||||
letter-spacing: -0.04px;
|
||||
}
|
||||
|
||||
|
|
@ -190,7 +191,7 @@
|
|||
font-family: var(--v2-font-family-sans);
|
||||
font-size: 13px;
|
||||
font-weight: 440;
|
||||
line-height: var(--v2-line-height-compact, 16px);
|
||||
line-height: var(--line-height-compact);
|
||||
letter-spacing: -0.04px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -609,7 +609,7 @@
|
|||
font-family: var(--font-family-sans);
|
||||
font-size: 13px;
|
||||
font-weight: var(--font-weight-regular, 440);
|
||||
line-height: 13px;
|
||||
line-height: var(--line-height-compact);
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--v2-text-text-faint, #808080);
|
||||
user-select: none;
|
||||
|
|
@ -1347,7 +1347,8 @@
|
|||
max-width: 100%;
|
||||
font-family: var(--font-family-sans);
|
||||
font-size: 13px;
|
||||
line-height: 13px;
|
||||
/* Loaded values truncate, so they require the full compact line box for descenders. */
|
||||
line-height: var(--line-height-compact);
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--v2-text-text-base);
|
||||
|
||||
|
|
|
|||
|
|
@ -354,7 +354,7 @@ export function createSessionTimelineRowRenderer(input: {
|
|||
<div class="flex min-h-5 min-w-0 items-center gap-2 overflow-hidden">
|
||||
<bdi
|
||||
dir="auto"
|
||||
class="shrink-0 text-[13px] font-[530] leading-none tracking-[-0.04px] text-v2-text-text-faint"
|
||||
class="shrink-0 text-[13px] font-[530] leading-text-compact tracking-[-0.04px] text-v2-text-text-faint"
|
||||
>
|
||||
{content().label}
|
||||
</bdi>
|
||||
|
|
@ -362,7 +362,7 @@ export function createSessionTimelineRowRenderer(input: {
|
|||
{(item) => (
|
||||
<bdi
|
||||
dir="auto"
|
||||
class="min-w-0 truncate text-[13px] font-[440] leading-none tracking-[-0.04px] text-v2-text-text-faint"
|
||||
class="min-w-0 truncate text-[13px] font-[440] leading-text-compact tracking-[-0.04px] text-v2-text-text-faint"
|
||||
>
|
||||
{item}
|
||||
</bdi>
|
||||
|
|
@ -379,7 +379,7 @@ export function createSessionTimelineRowRenderer(input: {
|
|||
<div
|
||||
data-slot="session-timeline-notice"
|
||||
data-type="location-switched"
|
||||
class={`flex h-7 w-full min-w-0 items-center gap-2 py-1 text-[13px] leading-none tracking-[-0.04px] text-v2-text-text-faint ${padding()}`}
|
||||
class={`flex h-7 w-full min-w-0 items-center gap-2 py-1 text-[13px] leading-text-compact tracking-[-0.04px] text-v2-text-text-faint ${padding()}`}
|
||||
>
|
||||
<Tooltip
|
||||
appearance="compact"
|
||||
|
|
|
|||
|
|
@ -1175,13 +1175,10 @@ ToolRegistry.register({
|
|||
>
|
||||
<div
|
||||
data-component="task-tool-delegating"
|
||||
class="flex h-9 w-fit max-w-full items-center gap-2 rounded-[8px] bg-v2-background-bg-layer-01 p-2.5"
|
||||
class="flex h-9 w-fit max-w-full items-center gap-2 rounded-[8px] bg-v2-background-bg-layer-01 p-2.5 text-[13px] font-[530] leading-text-compact tracking-[-0.04px]"
|
||||
>
|
||||
<Icon name="subagent" size="small" class="shrink-0 text-v2-icon-icon-faint" />
|
||||
<TextShimmer
|
||||
text={i18n.t("ui.tool.agent.delegating")}
|
||||
class="min-w-0 truncate text-[13px] font-[530] leading-none tracking-[-0.04px]"
|
||||
/>
|
||||
<TextShimmer text={i18n.t("ui.tool.agent.delegating")} class="min-w-0 truncate" />
|
||||
</div>
|
||||
</Show>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@
|
|||
font-style: normal;
|
||||
font-weight: 440;
|
||||
font-size: 11px;
|
||||
line-height: 100%;
|
||||
line-height: var(--line-height-tight);
|
||||
letter-spacing: 0.05px;
|
||||
text-transform: uppercase;
|
||||
font-variant-numeric: tabular-nums lining-nums;
|
||||
|
|
@ -398,7 +398,7 @@
|
|||
flex: none;
|
||||
font-size: 13px;
|
||||
font-weight: 530;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
letter-spacing: -0.04px;
|
||||
font-variant-numeric: tabular-nums lining-nums;
|
||||
color: var(--v2-text-text-base);
|
||||
|
|
@ -409,7 +409,7 @@
|
|||
flex: none;
|
||||
font-size: 13px;
|
||||
font-weight: 440;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
letter-spacing: -0.04px;
|
||||
font-variant-numeric: tabular-nums lining-nums;
|
||||
color: var(--v2-text-text-muted);
|
||||
|
|
@ -471,7 +471,7 @@
|
|||
flex: none;
|
||||
font-size: 13px;
|
||||
font-weight: 530;
|
||||
line-height: 100%;
|
||||
line-height: var(--line-height-compact);
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--v2-text-text-base);
|
||||
}
|
||||
|
|
@ -510,7 +510,7 @@
|
|||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 530;
|
||||
line-height: 100%;
|
||||
line-height: var(--line-height-compact);
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--v2-text-text-base);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,3 +10,10 @@
|
|||
- Translate whole UI phrases in context rather than substituting glossary words. Audit recurring concepts for consistency and review every exact-English value; retain it only when it is an intentional product/provider/tool name, URL, code token, keyboard legend, acronym, asset name, or established borrowing.
|
||||
- Record the corpora used and flag uncertain or regional terminology in review notes.
|
||||
- Also use the relevant language authority or official dictionary for the locale (for example RAE/Fundéu, FranceTerme, Duden, TDK, Kotus/Kielitoimiston sanakirja, Språkrådet/Bokmålsordboka, Rada Języka Polskiego/PWN, the Russian and Arabic language academies, the Ukrainian Orthography, Taiwan MOE dictionaries, or the Royal Society of Thailand). Treat the English dictionary as the semantic source of truth and preserve placeholders, code identifiers, product names, and keyboard labels.
|
||||
|
||||
## Typography
|
||||
|
||||
- Use the shared typography metrics instead of copying solid line heights from design exports: `--line-height-tight` is `12px`, `--line-height-compact` is `16px`, and `--line-height-base` is `20px`.
|
||||
- Inter text at `13px` must use at least the compact `16px` line height. A `13px` solid line box can clip `g`, `j`, `p`, `q`, and `y` when the text or an ancestor truncates or hides overflow.
|
||||
- Reserve `line-height: 1` and `leading-none` for non-text glyphs, icons, or deliberately reviewed display marks. Do not compensate text with transforms, negative margins, or clip-padding hacks.
|
||||
- `TextShimmer` inherits font metrics. Put typography overrides on its parent when they must supersede the component defaults.
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@
|
|||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 440;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--v2-text-text-base);
|
||||
font-variation-settings: "slnt" 0;
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: 530;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-tight);
|
||||
letter-spacing: 0.05px;
|
||||
color: var(--v2-text-text-faint);
|
||||
font-variation-settings: "slnt" 0;
|
||||
|
|
@ -138,7 +138,7 @@
|
|||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 530;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--v2-text-text-base);
|
||||
user-select: none;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
font-style: normal;
|
||||
font-weight: 530;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--v2-text-text-base);
|
||||
font-variation-settings: "slnt" 0;
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@
|
|||
font-style: normal;
|
||||
font-weight: 440;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
line-height: var(--line-height-compact);
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--v2-text-text-base);
|
||||
font-variation-settings: "slnt" 0;
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@
|
|||
|
||||
font-size: 13px;
|
||||
font-weight: 440;
|
||||
line-height: 100%;
|
||||
line-height: var(--line-height-compact);
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--menu-v2-fg);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
font-style: normal;
|
||||
font-weight: 440;
|
||||
font-size: 13px;
|
||||
line-height: 100%;
|
||||
line-height: var(--line-height-compact);
|
||||
letter-spacing: -0.04px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-variation-settings: "slnt" 0;
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@
|
|||
|
||||
font-size: 13px;
|
||||
font-weight: 440;
|
||||
line-height: 100%;
|
||||
line-height: var(--line-height-compact);
|
||||
letter-spacing: -0.04px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,9 @@
|
|||
--leading-lg: var(--line-height-large);
|
||||
--leading-xl: var(--line-height-x-large);
|
||||
--leading-2xl: var(--line-height-2x-large);
|
||||
--leading-text-tight: var(--line-height-tight);
|
||||
--leading-text-compact: var(--line-height-compact);
|
||||
--leading-text-base: var(--line-height-base);
|
||||
|
||||
--tracking-normal: var(--letter-spacing-normal);
|
||||
--tracking-tight: var(--letter-spacing-tight);
|
||||
|
|
|
|||
|
|
@ -131,6 +131,9 @@
|
|||
|
||||
--font-family-text: "Inter", sans-serif;
|
||||
--v2-font-family-sans: "Inter", sans-serif;
|
||||
--line-height-tight: 12px;
|
||||
--line-height-compact: 16px;
|
||||
--line-height-base: 20px;
|
||||
}
|
||||
|
||||
/* OS preference fallback (no JS needed) */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue