You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
247 lines
6.5 KiB
247 lines
6.5 KiB
<script lang="ts">
|
|
// Icon ↔ typography relationship study. For each step of the type scale we
|
|
// pair the REAL text (at its font-size) with an icon sized by each candidate
|
|
// model, so the optical balance can be judged by eye — not by formula.
|
|
//
|
|
// Fluid steps (lg/xl/xxl/xxxl) use their DESKTOP max here.
|
|
|
|
type Step = { name: string; font: number; lh: number };
|
|
const steps: Step[] = [
|
|
{ name: 'xxs', font: 10, lh: 15 },
|
|
{ name: 'xs', font: 12, lh: 18 },
|
|
{ name: 'sm', font: 14, lh: 20 },
|
|
{ name: 'md', font: 16, lh: 23 },
|
|
{ name: 'lg', font: 20, lh: 27 },
|
|
{ name: 'xl', font: 28, lh: 34 },
|
|
{ name: 'xxl', font: 48, lh: 50 }
|
|
];
|
|
|
|
// Today's icon scale (what ships now).
|
|
const CURRENT: Record<string, number> = {
|
|
xxs: 12,
|
|
xs: 14,
|
|
sm: 16,
|
|
md: 18,
|
|
lg: 20,
|
|
xl: 24,
|
|
xxl: 48
|
|
};
|
|
|
|
const grid4 = (n: number) => Math.round(n / 4) * 4;
|
|
|
|
type Model = { key: string; label: string; px: (s: Step) => number };
|
|
const models: Model[] = [
|
|
{ key: 'today', label: 'today', px: (s) => CURRENT[s.name] },
|
|
{ key: 'plus2', label: 'font + 2', px: (s) => s.font + 2 },
|
|
{ key: 'plus4', label: 'font + 4', px: (s) => s.font + 4 },
|
|
{ key: 'lh', label: 'line-height → grid-4', px: (s) => grid4(s.lh) }
|
|
];
|
|
|
|
let icon = $state<'house' | 'bell' | 'gear'>('house');
|
|
const icons = ['house', 'bell', 'gear'] as const;
|
|
|
|
const paths: Record<string, string> = {
|
|
house: 'M3 9.5 12 3l9 6.5V21a1 1 0 0 1-1 1h-5v-7H9v7H4a1 1 0 0 1-1-1z',
|
|
bell: 'M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9 M9.5 21a2.5 2.5 0 0 0 5 0',
|
|
gear: 'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z M19.4 13a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-2.82 1.17V22a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 7 20.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 3.6 15a1.65 1.65 0 0 0-1.51-1H2a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 3.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9c.2.61.78 1 1.42 1H22a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z'
|
|
};
|
|
</script>
|
|
|
|
{#snippet glyph(px: number)}
|
|
<svg
|
|
width={px}
|
|
height={px}
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
style="flex: 0 0 auto;"
|
|
>
|
|
<path d={paths[icon]} />
|
|
</svg>
|
|
{/snippet}
|
|
|
|
<div class="study">
|
|
<header>
|
|
<h1>Icon ↔ type relationship study</h1>
|
|
<p>
|
|
Each row pairs the real text (at its <code>font-size</code>) with the icon sized by each
|
|
model. Judge by eye: at every size, which icon sits in balance with the text — neither
|
|
shrunken nor overpowering? The answer may differ small vs large (e.g. <code>+2</code> low,
|
|
<code>line-height</code> high).
|
|
</p>
|
|
<div class="picker">
|
|
<span>glyph</span>
|
|
{#each icons as i}
|
|
<button class:active={icon === i} onclick={() => (icon = i)}>{i}</button>
|
|
{/each}
|
|
</div>
|
|
<div class="legend">
|
|
{#each models as m}<span class="chip {m.key}">{m.label}</span>{/each}
|
|
</div>
|
|
</header>
|
|
|
|
{#each steps as s}
|
|
<section class="step">
|
|
<div class="meta">
|
|
<strong>{s.name}</strong>
|
|
<span>font {s.font}px</span>
|
|
<span>line-height {s.lh}px</span>
|
|
</div>
|
|
<div class="cells">
|
|
{#each models as m}
|
|
{@const px = m.px(s)}
|
|
<div class="cell {m.key}">
|
|
<div class="sample" style="font-size: {s.font}px;">
|
|
{@render glyph(px)}
|
|
<span>Texto</span>
|
|
</div>
|
|
<div class="caption"><span class="dot {m.key}"></span>{m.label} · {px}px</div>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
</section>
|
|
{/each}
|
|
</div>
|
|
|
|
<style>
|
|
.study {
|
|
max-inline-size: 1100px;
|
|
margin-inline: auto;
|
|
padding: 2rem 1.5rem 5rem;
|
|
color: var(--color-content-primary);
|
|
}
|
|
header h1 {
|
|
font-size: 1.75rem;
|
|
font-weight: 600;
|
|
margin: 0 0 0.5rem;
|
|
}
|
|
header p {
|
|
color: var(--color-content-subtle);
|
|
max-inline-size: 70ch;
|
|
line-height: 1.55;
|
|
margin: 0 0 1rem;
|
|
}
|
|
code {
|
|
font-family: var(--font-mono, monospace);
|
|
font-size: 0.85em;
|
|
background: var(--color-surface-muted);
|
|
padding: 0.1em 0.35em;
|
|
border-radius: 4px;
|
|
}
|
|
.picker {
|
|
display: inline-flex;
|
|
gap: 0.35rem;
|
|
align-items: center;
|
|
margin-bottom: 0.75rem;
|
|
font-size: 0.8rem;
|
|
color: var(--color-content-subtle);
|
|
}
|
|
.picker button {
|
|
font: inherit;
|
|
padding: 0.2rem 0.6rem;
|
|
border-radius: 6px;
|
|
border: 1px solid var(--color-border-default);
|
|
background: var(--color-surface-default);
|
|
color: var(--color-content-subtle);
|
|
cursor: pointer;
|
|
}
|
|
.picker button.active {
|
|
background: var(--color-primary-solid);
|
|
color: var(--color-primary-contrast);
|
|
border-color: transparent;
|
|
}
|
|
.legend {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
font-size: 0.75rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
.chip {
|
|
padding: 0.15rem 0.5rem;
|
|
border-radius: 999px;
|
|
border: 1px solid var(--color-border-subtle);
|
|
color: var(--color-content-subtle);
|
|
}
|
|
.step {
|
|
border-top: 1px solid var(--color-border-subtle);
|
|
padding: 1.5rem 0;
|
|
}
|
|
.meta {
|
|
display: flex;
|
|
gap: 1rem;
|
|
align-items: baseline;
|
|
font-size: 0.8rem;
|
|
color: var(--color-content-subtle);
|
|
margin-bottom: 1rem;
|
|
font-family: var(--font-mono, monospace);
|
|
}
|
|
.meta strong {
|
|
font-size: 1rem;
|
|
color: var(--color-content-primary);
|
|
}
|
|
.cells {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
gap: 1rem;
|
|
align-items: end;
|
|
}
|
|
.cell {
|
|
background: var(--color-surface-raised);
|
|
border: 1px solid var(--color-border-subtle);
|
|
border-radius: 10px;
|
|
padding: 1rem;
|
|
overflow: hidden;
|
|
}
|
|
.sample {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5em;
|
|
line-height: 1.2;
|
|
color: var(--color-content-primary);
|
|
white-space: nowrap;
|
|
}
|
|
.caption {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
margin-top: 0.75rem;
|
|
font-size: 0.72rem;
|
|
color: var(--color-content-subtle);
|
|
font-family: var(--font-mono, monospace);
|
|
}
|
|
.dot {
|
|
inline-size: 8px;
|
|
block-size: 8px;
|
|
border-radius: 999px;
|
|
flex: 0 0 auto;
|
|
}
|
|
.dot.today {
|
|
background: var(--color-neutral-solid);
|
|
}
|
|
.dot.plus2 {
|
|
background: var(--color-affirm-solid);
|
|
}
|
|
.dot.plus4 {
|
|
background: var(--color-primary-solid);
|
|
}
|
|
.dot.lh {
|
|
background: var(--color-risk-solid);
|
|
}
|
|
.cell.today {
|
|
border-color: color-mix(in srgb, var(--color-neutral-solid) 35%, transparent);
|
|
}
|
|
.cell.plus2 {
|
|
border-color: color-mix(in srgb, var(--color-affirm-solid) 35%, transparent);
|
|
}
|
|
.cell.plus4 {
|
|
border-color: color-mix(in srgb, var(--color-primary-solid) 35%, transparent);
|
|
}
|
|
.cell.lh {
|
|
border-color: color-mix(in srgb, var(--color-risk-solid) 35%, transparent);
|
|
}
|
|
</style>
|