diff --git a/felsokning/app/package-lock.json b/felsokning/app/package-lock.json index 5ff7ecd..223201a 100644 --- a/felsokning/app/package-lock.json +++ b/felsokning/app/package-lock.json @@ -8,6 +8,8 @@ "name": "guidad-felsokning", "version": "0.0.0", "dependencies": { + "@fontsource/ibm-plex-mono": "^5.3.0", + "@fontsource/ibm-plex-sans": "^5.3.0", "@hookform/resolvers": "^3.10.0", "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.1.14", @@ -781,6 +783,24 @@ "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", "license": "MIT" }, + "node_modules/@fontsource/ibm-plex-mono": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/ibm-plex-mono/-/ibm-plex-mono-5.3.0.tgz", + "integrity": "sha512-eTgnZjZEGk1QtD3ZstF+Vclo2HLAni8YMy34/DxllwZvyz1lR/1RF/xTiAquOBO7MvqBx8D2Ig2WCPMVfdZu7Q==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/ibm-plex-sans": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/ibm-plex-sans/-/ibm-plex-sans-5.3.0.tgz", + "integrity": "sha512-CbE4CbbEEZJX860XyUiRpsksXIQR8Rp2XDva2VO53NJox9tVNtusrysd2x5YkUEY3ErQ66W1IiiQL8/wihhw5w==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@hookform/resolvers": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz", diff --git a/felsokning/app/package.json b/felsokning/app/package.json index ac198a6..065ac08 100644 --- a/felsokning/app/package.json +++ b/felsokning/app/package.json @@ -15,6 +15,8 @@ "genomgang": "node e2e/genomgang.mjs" }, "dependencies": { + "@fontsource/ibm-plex-mono": "^5.3.0", + "@fontsource/ibm-plex-sans": "^5.3.0", "@hookform/resolvers": "^3.10.0", "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.1.14", diff --git a/felsokning/app/src/alva/komponenter.tsx b/felsokning/app/src/alva/komponenter.tsx index b6b1e05..7409ef8 100644 --- a/felsokning/app/src/alva/komponenter.tsx +++ b/felsokning/app/src/alva/komponenter.tsx @@ -41,7 +41,7 @@ export const FARG = { export function Etikett({ children, ton = "steel" }: { children: ReactNode; ton?: "steel" | "blue" | "graphite" }) { const färg = ton === "blue" ? FARG.blue : ton === "graphite" ? FARG.graphite : FARG.steel; return ( -
+
{children}
); @@ -51,7 +51,7 @@ export function Rubrik({ children, niva = 2 }: { children: ReactNode; niva?: 1 | const storlek = niva === 1 ? "text-[32px] leading-[40px]" : niva === 2 ? "text-[22px] leading-[32px]" : "text-[16px] leading-[24px]"; const Tagg = (niva === 1 ? "h1" : niva === 2 ? "h2" : "h3") as "h1"; return ( - + {children} ); @@ -101,7 +101,7 @@ export function Statusmärke({ status }: { status: Status }) { const ram = status === "failed" || status === "blocked"; return ( {f.namn}
+
{f.namn}
{f.namn}
{f.syfte} @@ -333,7 +333,7 @@ export function Tabell({ kolumner, rader }: { kolumner: string[]; rader: ReactNo {k} @@ -369,7 +369,7 @@ export function Tabell({ kolumner, rader }: { kolumner: string[]; rader: ReactNo {rad.map((cell, j) => (
{kolumner[j]} diff --git a/felsokning/app/src/alva/typsnitt.css b/felsokning/app/src/alva/typsnitt.css new file mode 100644 index 0000000..4c18ba3 --- /dev/null +++ b/felsokning/app/src/alva/typsnitt.css @@ -0,0 +1,62 @@ +/* ALVA-SPEC-001 · paragraf 6 — bokstavsformen som fil. + * + * Fallkedjan i src/alva/system.ts sätter DIN 2014 först, därför att en + * organisation som licensierat DIN ska få DIN. Men de flesta har inte + * DIN installerat, och då föll ALVA tidigare ned till vad enheten råkade + * ha — Helvetica på en iPhone, Roboto på en Android, något annat på en + * verkstadsdator. Ett system som beskriver sig som en standard får inte + * se olika ut beroende på var det öppnas. + * + * IBM Plex Sans är därför inbakad. Den är specens tredje tillåtna snitt, + * fritt licensierad under SIL Open Font License (se LICENSE i den här + * katalogen), och delar DIN:s karaktär: rak, teknisk, byggd för + * ingenjörsdokument snarare än för löptext. + * + * Bara latinsk delmängd och tre vikter — 400 för text, 600 för etiketter + * och rubriker, 700 för ordmärket. Plus två vikter Plex Mono för + * beteckningar och mätvärden. Fem filer, drygt 100 kB. + * + * `font-display: swap` är avsiktligt: texten ska synas direkt i + * fallkedjans snitt och bytas när filen kommit fram. Ett tomt fält i en + * verkstad är sämre än ett fält i fel bokstavsform. + */ + +@font-face { + font-family: "IBM Plex Sans"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(./typsnitt/ibm-plex-sans-latin-400-normal.woff2) format("woff2"); +} + +@font-face { + font-family: "IBM Plex Sans"; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url(./typsnitt/ibm-plex-sans-latin-600-normal.woff2) format("woff2"); +} + +@font-face { + font-family: "IBM Plex Sans"; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(./typsnitt/ibm-plex-sans-latin-700-normal.woff2) format("woff2"); +} + +@font-face { + font-family: "IBM Plex Mono"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(./typsnitt/ibm-plex-mono-latin-400-normal.woff2) format("woff2"); +} + +@font-face { + font-family: "IBM Plex Mono"; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url(./typsnitt/ibm-plex-mono-latin-600-normal.woff2) format("woff2"); +} diff --git a/felsokning/app/src/alva/typsnitt/LICENSE b/felsokning/app/src/alva/typsnitt/LICENSE new file mode 100644 index 0000000..2ae8ef3 --- /dev/null +++ b/felsokning/app/src/alva/typsnitt/LICENSE @@ -0,0 +1,93 @@ +Copyright 2019 IBM Corp. All rights reserved. IBMPlexSans-Italic[wdth,wght].ttf: Copyright 2019 IBM Corp. All rights reserved. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/felsokning/app/src/alva/typsnitt/ibm-plex-mono-latin-400-normal.woff2 b/felsokning/app/src/alva/typsnitt/ibm-plex-mono-latin-400-normal.woff2 new file mode 100644 index 0000000..0804aaf Binary files /dev/null and b/felsokning/app/src/alva/typsnitt/ibm-plex-mono-latin-400-normal.woff2 differ diff --git a/felsokning/app/src/alva/typsnitt/ibm-plex-mono-latin-600-normal.woff2 b/felsokning/app/src/alva/typsnitt/ibm-plex-mono-latin-600-normal.woff2 new file mode 100644 index 0000000..67aeeb0 Binary files /dev/null and b/felsokning/app/src/alva/typsnitt/ibm-plex-mono-latin-600-normal.woff2 differ diff --git a/felsokning/app/src/alva/typsnitt/ibm-plex-sans-latin-400-normal.woff2 b/felsokning/app/src/alva/typsnitt/ibm-plex-sans-latin-400-normal.woff2 new file mode 100644 index 0000000..f0ee65d Binary files /dev/null and b/felsokning/app/src/alva/typsnitt/ibm-plex-sans-latin-400-normal.woff2 differ diff --git a/felsokning/app/src/alva/typsnitt/ibm-plex-sans-latin-600-normal.woff2 b/felsokning/app/src/alva/typsnitt/ibm-plex-sans-latin-600-normal.woff2 new file mode 100644 index 0000000..08c0d5a Binary files /dev/null and b/felsokning/app/src/alva/typsnitt/ibm-plex-sans-latin-600-normal.woff2 differ diff --git a/felsokning/app/src/alva/typsnitt/ibm-plex-sans-latin-700-normal.woff2 b/felsokning/app/src/alva/typsnitt/ibm-plex-sans-latin-700-normal.woff2 new file mode 100644 index 0000000..953f94f Binary files /dev/null and b/felsokning/app/src/alva/typsnitt/ibm-plex-sans-latin-700-normal.woff2 differ diff --git a/felsokning/app/src/felsokning/Bilagevisning.tsx b/felsokning/app/src/felsokning/Bilagevisning.tsx index 37616fe..4d133e3 100644 --- a/felsokning/app/src/felsokning/Bilagevisning.tsx +++ b/felsokning/app/src/felsokning/Bilagevisning.tsx @@ -28,7 +28,7 @@ function useKalla(bilaga: Bilaga, delningskod?: string): string | null | "laddar function Platshallare({ text }: { text: string }) { return ( -

+

{text}

); diff --git a/felsokning/app/src/felsokning/DelatArendeVy.tsx b/felsokning/app/src/felsokning/DelatArendeVy.tsx index dd6bf9a..a92b630 100644 --- a/felsokning/app/src/felsokning/DelatArendeVy.tsx +++ b/felsokning/app/src/felsokning/DelatArendeVy.tsx @@ -50,7 +50,7 @@ function BeslutsKnappar({ value={kommentar} maxLength={500} onChange={(e) => setKommentar(e.target.value)} - className="w-full rounded border border-[#ADADAD] bg-white px-2.5 py-1.5 text-[13px] focus:border-[#00437A] focus:outline-none" + className="w-full border border-[#ADADAD] bg-white px-2.5 py-1.5 text-[13px] focus:border-[#00437A] focus:outline-none" /> {fel &&

{fel}

} @@ -88,7 +88,7 @@ function IdentitetsPanel({ arende, avslutat }: { arende: Arende; avslutat: boole ["Status", avslutat ? "Avslutat" : "Felsökning pågår"], ]; return ( -
+
{idn.beskrivning &&

{idn.beskrivning}

}

{falt @@ -145,7 +145,7 @@ export function DelatArendeVy({ rubrik={b.objekt?.beskrivning ?? "Ärende"} hoger={ @@ -193,7 +193,7 @@ export function DelatArendeVy({ ); })()} -

+

{notis}

@@ -246,7 +246,7 @@ export function DelatArendeVy({ {klipp.map((v, i) => (
- +
{v.beskrivning}
))} @@ -257,7 +257,7 @@ export function DelatArendeVy({
{bilder.map((bild, i) => (
- +
{bild.beskrivning}
))} diff --git a/felsokning/app/src/felsokning/Mikrofon.tsx b/felsokning/app/src/felsokning/Mikrofon.tsx index 268ee86..4a7be7d 100644 --- a/felsokning/app/src/felsokning/Mikrofon.tsx +++ b/felsokning/app/src/felsokning/Mikrofon.tsx @@ -48,7 +48,7 @@ export function MikrofonKnapp({ paText }: { paText: (text: string) => void }) { type="button" onClick={() => (lyssnar ? stoppa() : starta())} aria-label={lyssnar ? "Stoppa inspelning" : "Starta röstinmatning"} - className={`min-h-8 min-w-11 rounded border px-3 text-[14px] font-semibold transition-colors ${ + className={`min-h-8 min-w-11 border px-3 text-[14px] font-semibold transition-colors ${ lyssnar ? "animate-pulse border-[#6E1414] bg-[#8B1A1A] text-white" : "border-[#ADADAD] bg-white text-[#1A1A1A] hover:border-[#00437A]" diff --git a/felsokning/app/src/felsokning/__tests__/alva-yta.test.ts b/felsokning/app/src/felsokning/__tests__/alva-yta.test.ts index fb0542f..209abbe 100644 --- a/felsokning/app/src/felsokning/__tests__/alva-yta.test.ts +++ b/felsokning/app/src/felsokning/__tests__/alva-yta.test.ts @@ -194,3 +194,47 @@ describe("ALVA-ytan bär sin egen typografi", () => { expect(css).toContain("tabular-nums"); }); }); + +// ---- Snittet måste följa med i bygget ---------------------------------- +// +// Fallkedjan säkrar ordningen men inte utfallet: utan DIN installerad +// föll ALVA ned till vad enheten råkade ha — Helvetica på en iPhone, +// Roboto på en Android. Ett system som beskriver sig som en standard får +// inte se olika ut beroende på var det öppnas. +// +// IBM Plex Sans är därför inbakad som fil. Den måste dessutom ligga som +// data i CSS:en och inte som en separat asset: ALVA publiceras bland +// annat som en självbärande sida bakom en CSP där varje extern begäran +// blockeras, och en @font-face mot en assetfil hade tyst fallit tillbaka +// till enhetens eget snitt — alltså precis det inbäddningen avskaffar. +describe("ALVA:s snitt följer med i bygget", () => { + const typsnitt = readFileSync("src/alva/typsnitt.css", "utf8"); + const vite = readFileSync("vite.config.ts", "utf8"); + + it("de tre textvikterna och två monovikterna finns som filer", () => { + for (const fil of [ + "ibm-plex-sans-latin-400-normal.woff2", + "ibm-plex-sans-latin-600-normal.woff2", + "ibm-plex-sans-latin-700-normal.woff2", + "ibm-plex-mono-latin-400-normal.woff2", + "ibm-plex-mono-latin-600-normal.woff2", + ]) { + expect(typsnitt, fil).toContain(fil); + expect(readFileSync(`src/alva/typsnitt/${fil}`).byteLength).toBeGreaterThan(8000); + } + }); + + it("licensen ligger bredvid filerna", () => { + // SIL Open Font License kräver att licenstexten följer med. + expect(readFileSync("src/alva/typsnitt/LICENSE", "utf8")).toMatch(/SIL OPEN FONT LICENSE/i); + }); + + it("bygget bakar in woff2 som data i stället för som separata filer", () => { + expect(vite).toContain("assetsInlineLimit"); + expect(vite).toContain(".woff2"); + }); + + it("ingen @font-face hämtar från en extern värd", () => { + expect(typsnitt).not.toMatch(/url\(\s*["']?https?:/); + }); +}); diff --git a/felsokning/app/src/felsokning/ui.tsx b/felsokning/app/src/felsokning/ui.tsx index b0438e0..1ed2cd6 100644 --- a/felsokning/app/src/felsokning/ui.tsx +++ b/felsokning/app/src/felsokning/ui.tsx @@ -90,7 +90,7 @@ export function StorKnapp({ type={type} onClick={onClick} disabled={disabled} - className={`min-h-10 w-full rounded px-3 py-1.5 text-[13px] font-semibold transition-colors disabled:cursor-not-allowed disabled:opacity-40 print:hidden ${farg} ${className}`} + className={`min-h-10 w-full px-3 py-1.5 text-[13px] font-semibold transition-colors disabled:cursor-not-allowed disabled:opacity-40 print:hidden ${farg} ${className}`} > {children} @@ -99,7 +99,7 @@ export function StorKnapp({ export function Panel({ rubrik, children }: { rubrik?: string; children: ReactNode }) { return ( -
+
{rubrik && (

{rubrik} @@ -147,7 +147,7 @@ export function TextFalt({ const vardeRef = useRef(varde); vardeRef.current = varde; const klass = - "w-full rounded border border-[#ADADAD] bg-white px-2.5 py-1.5 text-[13px] text-[#1A1A1A] placeholder-[#9AA0A6] focus:border-[#00437A] focus:outline-none focus:ring-1 focus:ring-[#00437A]"; + "w-full border border-[#ADADAD] bg-white px-2.5 py-1.5 text-[13px] text-[#1A1A1A] placeholder-[#9AA0A6] focus:border-[#00437A] focus:outline-none focus:ring-1 focus:ring-[#00437A]"; return (

-
+
{tal.riktning === "hogre" ? "↑ bättre" : "↓ bättre"} · {tal.underlag}

diff --git a/felsokning/app/src/pages/alva/Ansokan.tsx b/felsokning/app/src/pages/alva/Ansokan.tsx index 135cdfa..eb2a848 100644 --- a/felsokning/app/src/pages/alva/Ansokan.tsx +++ b/felsokning/app/src/pages/alva/Ansokan.tsx @@ -89,7 +89,7 @@ export default function Ansokan() { {FALT.map((f) => (

-
))}
-