diff --git a/felsokning/app/src/alva/system.ts b/felsokning/app/src/alva/system.ts index 2b3f76d..1f960c5 100644 --- a/felsokning/app/src/alva/system.ts +++ b/felsokning/app/src/alva/system.ts @@ -168,3 +168,24 @@ export const PLATTFORMSVERSION = "ALVA 1.0"; export const RUTNÄT = 8; export const steg = (antal: number) => `${antal * RUTNÄT}px`; + +// ---- Typografi (ALVA-SPEC-001 · paragraf 6) --------------------------- +// +// En familj genomgående, åtskild med vikt, storlek, spärrning och +// versalisering. Produktionssnittet är DIN 2014; tillåtna alternativ är +// FF DIN och IBM Plex Sans. Ordningen nedan är därför inte godtycklig: +// en organisation som licensierat DIN får DIN, övriga faller ned genom +// grotesker som delar dess karaktär — aldrig till en antikva. +// +// Detta var länge ett avsteg i praktiken. ALVA-ytan satte inget eget +// snitt och ärvde därför värdapplikationens `Crimson Text` — en antikva. +// Specen beskrev ett tyskt industrisystem medan skärmen visade en +// bokstavsform från en helt annan tradition, och det syntes direkt på en +// telefon där ingenting annat konkurrerade om intrycket. +export const TYPSNITT = + '"DIN 2014", "FF DIN", "IBM Plex Sans", "Inter", "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif'; + +// Identifierare och mätvärden sätts med fast teckenbredd så att siffror +// står i kolumn — beteckningar jämförs oftare än de läses. +export const TYPSNITT_MONO = + 'ui-monospace, "IBM Plex Mono", "SF Mono", "Cascadia Mono", Menlo, Consolas, "Liberation Mono", monospace'; diff --git a/felsokning/app/src/felsokning/__tests__/alva-yta.test.ts b/felsokning/app/src/felsokning/__tests__/alva-yta.test.ts index b75d957..fb0542f 100644 --- a/felsokning/app/src/felsokning/__tests__/alva-yta.test.ts +++ b/felsokning/app/src/felsokning/__tests__/alva-yta.test.ts @@ -156,3 +156,41 @@ describe("ALVA-ytan håller sig inom skärmbredden", () => { expect(komponenter).toContain("min-w-0 flex-1"); }); }); + +// ---- Typografin är specens, inte värdapplikationens -------------------- +// +// ALVA-ytan satte länge inget eget snitt och ärvde därför butikens +// `Crimson Text` — en antikva. Specen (ALVA-SPEC-001, paragraf 6) +// föreskriver DIN 2014 med FF DIN och IBM Plex Sans som tillåtna +// alternativ. Skillnaden är inte kosmetisk: bokstavsformen är det första +// som säger vilken tradition ett verktyg tillhör, och på en telefon där +// ingenting annat konkurrerar om intrycket var det allt man såg. +describe("ALVA-ytan bär sin egen typografi", () => { + const css = readFileSync("src/index.css", "utf8"); + const ram = filer.find(([n]) => n.endsWith("pages/alva/Ram.tsx"))![1]; + const system = filer.find(([n]) => n.endsWith("alva/system.ts"))![1]; + + it("ramen märker ytan så att kaskaden når den", () => { + expect(ram).toContain("alva-yta"); + }); + + it("snittet är deklarerat i systemfilen med specens ordning", () => { + const stack = system.match(/export const TYPSNITT =[\s\S]*?;/)?.[0] ?? ""; + expect(stack).toContain("DIN 2014"); + expect(stack).toContain("FF DIN"); + expect(stack).toContain("IBM Plex Sans"); + // Fallkedjan får aldrig sluta i en antikva. `sans-serif` är den + // generiska groteskfamiljen och alltså inte det som avses. + expect(stack).not.toMatch(/(? { + const regel = css.match(/\.alva-yta[\s\S]*?\{[\s\S]*?\}/)?.[0] ?? ""; + expect(regel).toContain("DIN 2014"); + expect(css).toContain(".alva-yta h1"); + }); + + it("siffror sätts med fast bredd så beteckningar går att jämföra", () => { + expect(css).toContain("tabular-nums"); + }); +}); diff --git a/felsokning/app/src/index.css b/felsokning/app/src/index.css index febb375..39e7691 100644 --- a/felsokning/app/src/index.css +++ b/felsokning/app/src/index.css @@ -156,6 +156,57 @@ } } +/* ---- ALVA-ytan (ALVA-SPEC-001 · paragraf 6) -------------------------- + * + * Värdapplikationen sätter en antikva på body. ALVA ärvde den, vilket är + * fel tradition för ett system som beskriver sig som ett tyskt + * industriverktyg. Reglerna nedan gäller bara ALVA-ytan; butiken lämnas + * som den är. + * + * Snittet deklareras i src/alva/system.ts och speglas här därför att + * kaskaden måste nå även de element komponenterna inte själva sätter + * stil på — rubriker fångas annars av värdapplikationens h1–h6-regel. + */ +.alva-yta, +.alva-yta h1, +.alva-yta h2, +.alva-yta h3, +.alva-yta h4, +.alva-yta h5, +.alva-yta h6, +.alva-yta button, +.alva-yta input, +.alva-yta select, +.alva-yta textarea { + font-family: + "DIN 2014", + "FF DIN", + "IBM Plex Sans", + "Inter", + "Helvetica Neue", + Helvetica, + Arial, + system-ui, + sans-serif; +} + +/* Siffror står i kolumn. En beteckning jämförs oftare än den läses. */ +.alva-yta { + font-variant-numeric: tabular-nums; +} + +.alva-yta .font-mono { + font-family: + ui-monospace, + "IBM Plex Mono", + "SF Mono", + "Cascadia Mono", + Menlo, + Consolas, + "Liberation Mono", + monospace; +} + @layer utilities { .animate-fade-up { animation: fadeUp 0.8s ease-out forwards; diff --git a/felsokning/app/src/pages/alva/Ram.tsx b/felsokning/app/src/pages/alva/Ram.tsx index f9b91dd..d04a9a0 100644 --- a/felsokning/app/src/pages/alva/Ram.tsx +++ b/felsokning/app/src/pages/alva/Ram.tsx @@ -28,7 +28,9 @@ export function Ram({ children, portal = false }: { children: ReactNode; portal? const lankar = portal ? PORTAL : PUBLIKT; return ( -