diff --git a/docs/feature-specs/program-certificates.md b/docs/feature-specs/program-certificates.md
index 2514e80aa..f91759e20 100644
--- a/docs/feature-specs/program-certificates.md
+++ b/docs/feature-specs/program-certificates.md
@@ -61,3 +61,13 @@ drag/resize/границы/клавиатура; длинная кириллиц
размер подписи, масштаб и PDF используют одинаковые размеры листа. Старые
шаблоны без `orientation` считаются альбомными. Изменение шаблона не меняет
ранее выданные PDF. Для выкладки сначала требуется API с поддержкой ориентации.
+
+## Шрифты текста
+
+В панели выбранного текстового элемента доступно компактное поле «Шрифт»:
+Mont, PT Serif и Lobster. У каждого элемента свой выбор; подпись остается
+изображением. Шрифт сохраняется вместе с геометрией, участвует в истории и
+подставляется в пробный и выдаваемый PDF. Старые шаблоны используют Mont.
+Файлы хранятся локально, PDF встраивает выбранный шрифт, подгонка длинного ФИО
+использует его метрики. Семейство не меняет элементы управления и подписи
+редактора. Для выкладки требуется сначала парный API с `layout.*.font_family`.
diff --git a/docs/qa/certificates-fonts-20261025.md b/docs/qa/certificates-fonts-20261025.md
new file mode 100644
index 000000000..3f95dc072
--- /dev/null
+++ b/docs/qa/certificates-fonts-20261025.md
@@ -0,0 +1,51 @@
+
+
+# Certificate text fonts — verification 2026-10-25
+
+Scope: a per-element font selector in the existing certificate canvas. The rest
+of the product keeps Mont and the established management controls. Choices are
+Mont, PT Serif and Lobster, with bundled Cyrillic faces and no font CDN requests.
+The selector is 132 × 32px with 12px type; it wraps with the existing tools on mobile.
+
+## Behavior and compatibility
+
+- Each text box stores `fontFamily`; image signatures cannot use a text font.
+- Changing a family preserves geometry and font size and participates in undo/redo.
+- Font choice survives save/reload, text moves, resizes and orientation changes.
+- Font loading completes before the final text-fit measurement. A load failure is
+ shown explicitly. Selection labels and placeholders retain Mont.
+- Existing drafts and snapshots use Mont. The API preserves each saved family when
+ an older client omits the field and rejects unknown values and arbitrary font URLs.
+- Browser WOFF2 files are container conversions of the exact TTFs used for PDF
+ measurements and embedding. PT Serif uses 400/700, Lobster its actual 400 face
+ without synthetic bold, and Mont the existing 400/600 faces. Font provenance and
+ attribution files accompany the assets in both repositories.
+
+## Verification
+
+- 32 Angular component/layout tests pass, including initial selected font, switching,
+ history, save payloads, geometry preservation, image and preview restrictions.
+- 35 backend certificate tests: 33 pass, 2 PostgreSQL locking tests skip on local
+ SQLite. Tests cover per-element round trip, old clients, legacy defaults, rejected
+ families, long Cyrillic fitting in all three fonts and both A4 orientations, and
+ actual embedded font resources in native WeasyPrint PDF output.
+- Angular build, targeted ESLint, Stylelint, Prettier, Black, flake8 and diff checks.
+- Actual Angular build and Django API on disposable localhost program 1: switched
+ all three families, downloaded preview PDFs, saved/reloaded Lobster and used
+ undo/redo. Long-name preview and compact controls checked at desktop and 390/320.
+- Six native long-name PDFs (three fonts × two orientations) rendered with Poppler
+ and visually inspected. Each has one A4 page, an embedded selected font, intact
+ extracted Cyrillic name and no clipped glyphs.
+
+A browser integration check found that `[value]` plus dynamically created options
+displayed Mont after reload even though the saved font was Lobster. The selector
+now uses the existing Angular forms value accessor and the initial saved selection
+has a regression assertion. This was fixed before PR creation.
+
+Evidence is outside Git in `design-artifacts/program-certificate-fonts-20261025/`:
+browser/PDF checks, preview PDFs, six long-name renders, desktop/mobile screenshots
+and command logs. No real dev data or participant email was changed. Native touch,
+other browser engines and delivery activation were not exercised.
+
+Deploy the paired API first, then frontend. No database migration or regeneration
+of previously issued PDF files is needed. Custom font-file upload is outside this change.
diff --git a/projects/social_platform/src/app/domain/program/program-certificates.model.ts b/projects/social_platform/src/app/domain/program/program-certificates.model.ts
index 476c75a67..ce67730b2 100644
--- a/projects/social_platform/src/app/domain/program/program-certificates.model.ts
+++ b/projects/social_platform/src/app/domain/program/program-certificates.model.ts
@@ -37,6 +37,15 @@ export interface CertificateBox {
width: number;
height: number;
fontSize: number;
+ fontFamily?: "mont" | "pt-serif" | "lobster";
+}
+export const CERTIFICATE_FONTS = [
+ { key: "mont", label: "Mont", family: "Mont", boldWeight: 600 },
+ { key: "pt-serif", label: "PT Serif", family: "Certificate PT Serif", boldWeight: 700 },
+ { key: "lobster", label: "Lobster", family: "Certificate Lobster", boldWeight: 400 },
+] as const;
+export function certificateFont(key: CertificateBox["fontFamily"]) {
+ return CERTIFICATE_FONTS.find(font => font.key === key) ?? CERTIFICATE_FONTS[0];
}
export interface CertificateAsset {
id: number;
diff --git a/projects/social_platform/src/app/ui/pages/program/management/certificates/certificate-canvas.component.html b/projects/social_platform/src/app/ui/pages/program/management/certificates/certificate-canvas.component.html
index 98e5c4971..4feebad35 100644
--- a/projects/social_platform/src/app/ui/pages/program/management/certificates/certificate-canvas.component.html
+++ b/projects/social_platform/src/app/ui/pages/program/management/certificates/certificate-canvas.component.html
@@ -1,5 +1,18 @@
Не удалось загрузить шрифты. Обновите страницу.
+}
();
readonly keys = CERTIFICATE_ELEMENTS;
readonly labels = CERTIFICATE_LABELS;
+ readonly fonts = CERTIFICATE_FONTS;
+ readonly fontLoadError = signal(false);
readonly zoom = signal(1);
readonly dragging = signal(false);
readonly viewport = viewChild>("viewport");
@@ -81,11 +86,12 @@ export class CertificateCanvasComponent {
const result = {} as Record;
const cache = new Map();
for (const key of this.keys) {
- const weight = key === "name" || key === "title" ? 600 : 400;
+ const font = this.font(key);
+ const weight = this.fontWeight(key);
const measure = (value: string) => {
- const id = `${weight}:${value}`;
+ const id = `${font.key}:${weight}:${value}`;
if (!cache.has(id)) {
- if (this.context) this.context.font = `${weight} 1000px Mont`;
+ if (this.context) this.context.font = `${weight} 1000px "${font.family}"`;
cache.set(id, this.context?.measureText(value).width ?? Array.from(value).length * 550);
}
return cache.get(id)!;
@@ -133,9 +139,24 @@ export class CertificateCanvasComponent {
if (this.viewport()) observer?.observe(this.viewport()!.nativeElement);
window.addEventListener("resize", measure);
measure();
- void document.fonts?.ready.then(() => {
- if (!this.destroy.destroyed) this.fontsReady.set(true);
- });
+ if (document.fonts) {
+ void document.fonts.ready
+ .then(() =>
+ Promise.all(
+ this.fonts.flatMap(font =>
+ [...new Set([400, font.boldWeight])].map(weight =>
+ document.fonts.load(`${weight} 1000px "${font.family}"`),
+ ),
+ ),
+ ),
+ )
+ .then(() => {
+ if (!this.destroy.destroyed) this.fontsReady.set(true);
+ })
+ .catch(() => {
+ if (!this.destroy.destroyed) this.fontLoadError.set(true);
+ });
+ }
this.destroy.onDestroy(() => {
observer?.disconnect();
window.removeEventListener("resize", measure);
@@ -145,6 +166,19 @@ export class CertificateCanvasComponent {
box(): CertificateBox {
return this.draft().layout[this.selected()];
}
+ font(key: CertificateElement) {
+ return certificateFont(this.draft().layout[key].fontFamily);
+ }
+ fontWeight(key: CertificateElement): number {
+ return key === "name" || key === "title" ? this.font(key).boldWeight : 400;
+ }
+ familyChanged(value: string): void {
+ if (this.disabled() || this.preview() || this.selected() === "signature") return;
+ const font = this.fonts.find(font => font.key === value);
+ if (!font || font.key === certificateFont(this.box().fontFamily).key) return;
+ this.interactionStart.emit();
+ this.update(this.selected(), { ...this.box(), fontFamily: font.key });
+ }
visible(key: CertificateElement): boolean {
return !hiddenCertificateElements(this.draft()).includes(
key as Exclude,
diff --git a/projects/social_platform/src/app/ui/pages/program/management/certificates/certificates.component.spec.ts b/projects/social_platform/src/app/ui/pages/program/management/certificates/certificates.component.spec.ts
index b2446ca97..c784c8599 100644
--- a/projects/social_platform/src/app/ui/pages/program/management/certificates/certificates.component.spec.ts
+++ b/projects/social_platform/src/app/ui/pages/program/management/certificates/certificates.component.spec.ts
@@ -1,11 +1,13 @@
/** @format */
import { TestBed } from "@angular/core/testing";
+import { By } from "@angular/platform-browser";
import { of, Subject, throwError } from "rxjs";
import { describe, expect, it, vi } from "vitest";
import { ProgramCertificatesRepositoryPort } from "@domain/program/ports/program-certificates.repository.port";
import { ProgramCertificatesUseCase } from "@api/program/use-cases/program-certificates.use-case";
import { CertificateDraft, CertificateSettings } from "@domain/program/program-certificates.model";
import { ManagementCertificatesComponent } from "./certificates.component";
+import { CertificateCanvasComponent } from "./certificate-canvas.component";
const keys = [
"title",
"subtitle",
@@ -80,6 +82,69 @@ async function setup(data = settings()) {
return { fixture, component: fixture.componentInstance, repository };
}
describe("Program certificates", () => {
+ it("changes only the selected element font with history, saves it and keeps geometry", async () => {
+ const { fixture, component, repository } = await setup();
+ const initial = structuredClone(component.draft()!.layout);
+ const select = fixture.nativeElement.querySelector(
+ ".canvas__family select",
+ ) as HTMLSelectElement;
+ expect(select.value).toBe("mont");
+ select.value = "pt-serif";
+ select.dispatchEvent(new Event("change"));
+ fixture.detectChanges();
+ expect(component.draft()!.layout.name).toEqual({ ...initial.name, fontFamily: "pt-serif" });
+ expect(component.draft()!.layout.director).toEqual(initial.director);
+ expect(fixture.nativeElement.querySelector(".canvas__element").style.fontFamily).toContain(
+ "Certificate PT Serif",
+ );
+ component.undo();
+ expect(component.draft()!.layout).toEqual(initial);
+ component.redo();
+ expect(component.draft()!.layout.name.fontFamily).toBe("pt-serif");
+ await component.save();
+ expect(repository.save).toHaveBeenCalledWith(
+ 9,
+ 1,
+ expect.objectContaining({
+ layout: expect.objectContaining({
+ name: expect.objectContaining({ fontFamily: "pt-serif" }),
+ }),
+ }),
+ );
+ expect(component.dirty()).toBe(false);
+ fixture.destroy();
+ });
+ it("preserves the saved font while centering, and blocks font changes for images and preview", async () => {
+ const data = settings();
+ data.draft.layout.name.fontFamily = "lobster";
+ const { fixture, component } = await setup(data);
+ expect(
+ (fixture.nativeElement.querySelector(".canvas__family select") as HTMLSelectElement).value,
+ ).toBe("lobster");
+ const canvas = fixture.debugElement.query(By.directive(CertificateCanvasComponent))
+ .componentInstance as CertificateCanvasComponent;
+ canvas.center();
+ fixture.detectChanges();
+ expect(component.draft()!.layout.name.fontFamily).toBe("lobster");
+ expect(canvas.fontWeight("name")).toBe(400);
+ component.selected.set("signature");
+ fixture.detectChanges();
+ await fixture.whenStable();
+ fixture.detectChanges();
+ expect(
+ (fixture.nativeElement.querySelector(".canvas__family select") as HTMLSelectElement).disabled,
+ ).toBe(true);
+ canvas.familyChanged("pt-serif");
+ expect(component.draft()!.layout.signature.fontFamily).toBeUndefined();
+ component.selected.set("name");
+ component.editorPreview.set(true);
+ fixture.detectChanges();
+ await fixture.whenStable();
+ fixture.detectChanges();
+ canvas.familyChanged("pt-serif");
+ expect(component.draft()!.layout.name.fontFamily).toBe("lobster");
+ fixture.destroy();
+ });
it("defaults legacy templates to landscape and saves portrait with undo and redo", async () => {
const { fixture, component, repository } = await setup();
expect(component.draft()!.orientation).toBe("landscape");
diff --git a/projects/social_platform/src/assets/font/certificates/Lobster-OFL.txt b/projects/social_platform/src/assets/font/certificates/Lobster-OFL.txt
new file mode 100644
index 000000000..a41143009
--- /dev/null
+++ b/projects/social_platform/src/assets/font/certificates/Lobster-OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2010 The Lobster Project Authors (https://github.com/impallari/The-Lobster-Font), with Reserved Font Name "Lobster".
+
+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/projects/social_platform/src/assets/font/certificates/Lobster-Regular.woff2 b/projects/social_platform/src/assets/font/certificates/Lobster-Regular.woff2
new file mode 100644
index 000000000..cad21d8c5
Binary files /dev/null and b/projects/social_platform/src/assets/font/certificates/Lobster-Regular.woff2 differ
diff --git a/projects/social_platform/src/assets/font/certificates/PTSerif-Bold.woff2 b/projects/social_platform/src/assets/font/certificates/PTSerif-Bold.woff2
new file mode 100644
index 000000000..8ab407ad4
Binary files /dev/null and b/projects/social_platform/src/assets/font/certificates/PTSerif-Bold.woff2 differ
diff --git a/projects/social_platform/src/assets/font/certificates/PTSerif-OFL.txt b/projects/social_platform/src/assets/font/certificates/PTSerif-OFL.txt
new file mode 100644
index 000000000..e9f47cb8b
--- /dev/null
+++ b/projects/social_platform/src/assets/font/certificates/PTSerif-OFL.txt
@@ -0,0 +1,93 @@
+Copyright (c) 2010, ParaType Ltd. (http://www.paratype.com/public),
+with Reserved Font Names "PT Sans", "PT Serif" and "ParaType".
+
+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/projects/social_platform/src/assets/font/certificates/PTSerif-Regular.woff2 b/projects/social_platform/src/assets/font/certificates/PTSerif-Regular.woff2
new file mode 100644
index 000000000..dc3c3acd5
Binary files /dev/null and b/projects/social_platform/src/assets/font/certificates/PTSerif-Regular.woff2 differ
diff --git a/projects/social_platform/src/assets/font/certificates/README.md b/projects/social_platform/src/assets/font/certificates/README.md
new file mode 100644
index 000000000..bd981b23a
--- /dev/null
+++ b/projects/social_platform/src/assets/font/certificates/README.md
@@ -0,0 +1,23 @@
+
+
+# Certificate fonts
+
+WOFF2 files are container conversions of Google Fonts TTF files from commit
+[`e7db6ca603fd4862010383ff17ffd6cc4ffac1c0`](https://github.com/google/fonts/tree/e7db6ca603fd4862010383ff17ffd6cc4ffac1c0).
+The paired API bundles the original TTFs for identical text metrics and PDF embedding.
+Font outlines, names, character coverage and layout tables are retained.
+
+| Frontend file | Upstream TTF | TTF SHA-256 |
+| --------------------- | ------------------------------------ | ---------------------------------------------------------------- |
+| PTSerif-Regular.woff2 | ofl/ptserif/PT_Serif-Web-Regular.ttf | a4951fade06ff8f09b7673aa81ffb65a8cd409e24d3289a6dc670bc4dda2557a |
+| PTSerif-Bold.woff2 | ofl/ptserif/PT_Serif-Web-Bold.ttf | 038ba7336bd7ea14f12ad155bed51a4345cac5153275d521dec3ba04021c526e |
+| Lobster-Regular.woff2 | ofl/lobster/Lobster-Regular.ttf | d6568e697fd50cedc0be04d8aae4127fe95add607e7bff954ca88604be80c205 |
+
+PT Serif: ParaType. [License and attribution](PTSerif-OFL.txt).
+Lobster: The Lobster Project Authors. [License and attribution](Lobster-OFL.txt).
+Mont continues to use the existing product assets. All font requests are served
+by the application, with no Google Fonts CDN dependency. Added WOFF2 files total
+297,460 bytes and are loaded when the certificate canvas opens.
+
+The CSS aliases `Certificate PT Serif` and `Certificate Lobster` apply only to
+certificate text. Product controls and editor selection labels keep Mont.