Local webfont 0123456789
First 强调 link
Base
Clear
Inline 中文 tail
Authored
Third
底部对齐 A long line remains unwrapped when the source text body disables wrapping.
中文 First marker follows this visible run. This long paragraph wraps onto another line so the hanging marker stays outside the body and never becomes part of the text. This line keeps the same indentation.
Explicit symbol font, size and color.
Soft break
No marker at level three.
Ninth level returns to the base marker style.
Local cancellation.
A
B
C
Plain
D
E
F
Restart
After
Independent
A 中文 D
A B C
A B C
A B C
A B
C D
A AB中文 CD
A AB中文 CD
A E
A BB
X YY
A MMMMMMMMMMMMMMMMMMMMMMMMMMMM
A F G H
A {{ "BB\\nX" }} YY
AAAA BBBB CCCC
AAAA BBBB CCCC
AAAA BBBB CCCC
AAAA BBBB
AAAAAAAAAAAAAAAAAAAA
Small
Large
Soft
Points
Both 中文
Strike only
Underline only
Plain HTML underline HTML strike
Local strike Cancelled Resumed
Base Strike Neither Both
Decorated bullet body
Before
After Link
A B
Ordinary 中文 strike
\n' expect((await request.put(`http://127.0.0.1:4317/api/v1/decks/${deck.id}/markdown`, { data: { expectedRevision: deck.revision, content } })).ok()).toBe(true) await page.goto('/') - await page.getByLabel('Preview deck').click() - await page.getByRole('option', { name: 'slides', exact: true }).click() + await page.getByLabel('Preview deck').selectOption({ label: 'slides' }) const preview = page.getByTitle('Slidev preview').contentFrame(), root = preview.locator('.source-layout'), frame = preview.locator('#decorations') await expect(frame).toBeVisible() const projected = () => frame.locator('[data-fastppt-text-decoration-run]').evaluateAll(elements => elements.map(element => ({ text: element.textContent, lines: getComputedStyle(element).textDecorationLine }))) @@ -1624,8 +1614,7 @@ test('preserves signed source letter spacing through levels, inline edits, tabs, const content = '---\ntheme: slidev-theme-academy\nlayout: source-letter-spacing\n---\n\n::ph-0::\n\n\n' + [1, 2, 3].map(index => `\n::ph-${index}::\n\nAAAA BBBB CCCC
Ordinary中文
\n' expect((await request.put(`http://127.0.0.1:4317/api/v1/decks/${deck.id}/markdown`, { data: { expectedRevision: deck.revision, content } })).ok()).toBe(true) await page.goto('/') - await page.getByLabel('Preview deck').click() - await page.getByRole('option', { name: 'slides', exact: true }).click() + await page.getByLabel('Preview deck').selectOption({ label: 'slides' }) const preview = page.getByTitle('Slidev preview').contentFrame(), root = preview.locator('.source-layout'), frame = preview.locator('#spacing') await expect(frame).toBeVisible() const computedSpacing = () => frame.locator('p').evaluateAll(paragraphs => paragraphs.map(element => Number(((parseFloat(getComputedStyle(element).letterSpacing) || 0) * 960 / parseFloat(getComputedStyle(element.closest('.source-layout')!).width)).toFixed(2)))) @@ -1691,8 +1680,7 @@ test('preserves source baseline ratios without shrinking fonts or double-shiftin const content = '---\ntheme: slidev-theme-academy\nlayout: source-baseline\n---\n\n::ph-0::\n\n\n\n::ph-1::\n\nA AAAA BBBB CCCC
End
Ordinary中文
Ordinary中文
\n' expect((await request.put(`http://127.0.0.1:4317/api/v1/decks/${deck.id}/markdown`, { data: { expectedRevision: deck.revision, content } })).ok()).toBe(true) await page.goto('/') - await page.getByLabel('Preview deck').click() - await page.getByRole('option', { name: 'slides', exact: true }).click() + await page.getByLabel('Preview deck').selectOption({ label: 'slides' }) const preview = page.getByTitle('Slidev preview').contentFrame(), root = preview.locator('.source-layout'), frame = preview.locator('#baseline') await expect(frame).toBeVisible() const geometry = () => frame.locator('p').evaluateAll(paragraphs => paragraphs.map(element => { @@ -1800,8 +1788,7 @@ test('preserves kerning thresholds through font boundaries, resizing, editing an const content = '---\ntheme: slidev-theme-academy\nlayout: source-kerning\n---\n\n::ph-0::\n\n\n\n::ph-1::\n\nAVATAR To WA
AVATAR To WA
Ordinary中文
\n' expect((await request.put(`http://127.0.0.1:4317/api/v1/decks/${deck.id}/markdown`, { data: { expectedRevision: deck.revision, content } })).ok()).toBe(true) await page.goto('/') - await page.getByLabel('Preview deck').click() - await page.getByRole('option', { name: 'slides', exact: true }).click() + await page.getByLabel('Preview deck').selectOption({ label: 'slides' }) const preview = page.getByTitle('Slidev preview').contentFrame(), root = preview.locator('.source-layout'), frame = preview.locator('#kerning') await expect(frame).toBeVisible() const modes = () => frame.locator('[data-fastppt-text-kerning]').evaluateAll(elements => elements.filter(element => element.tagName === 'SPAN').map(element => ({ text: element.textContent, mode: getComputedStyle(element).fontKerning }))) @@ -1882,8 +1869,7 @@ test('preserves original capitalization text through templates, Unicode expansio const content = '---\ntheme: slidev-theme-academy\nlayout: source-capitalization\n---\n\n::ph-0::\n\n\n\n::ph-1::\n\nStraße StraßeStraße
Straße中文
\n' expect((await request.put(`http://127.0.0.1:4317/api/v1/decks/${deck.id}/markdown`, { data: { expectedRevision: deck.revision, content } })).ok()).toBe(true) await page.goto('/') - await page.getByLabel('Preview deck').click() - await page.getByRole('option', { name: 'slides', exact: true }).click() + await page.getByLabel('Preview deck').selectOption({ label: 'slides' }) const preview = page.getByTitle('Slidev preview').contentFrame(), root = preview.locator('.source-layout'), frame = preview.locator('#capitals') await expect(frame).toBeVisible() const modes = () => frame.locator('p').evaluateAll(elements => elements.slice(0, 4).map(element => { diff --git a/tests/e2e/specs/library-workspace.spec.ts b/tests/e2e/specs/library-workspace.spec.ts index 0136dec5..5ef4f7e5 100644 --- a/tests/e2e/specs/library-workspace.spec.ts +++ b/tests/e2e/specs/library-workspace.spec.ts @@ -51,7 +51,8 @@ test('keeps seven workspace tabs and direct creation actions and a compact previ await expect(page.getByRole('heading', { name: '讲稿备注' })).toBeVisible() await page.keyboard.press('Control+Shift+P') const commands = page.getByRole('dialog', { name: '命令面板' }) - await expect(commands.getByRole('button', { name: /演示/ })).toHaveCount(0) + await expect(commands.getByRole('button', { name: '新建演示文稿' })).toBeVisible() + await expect(commands.getByRole('button', { name: /^(进入演示|开始演示|打开演示)$/ })).toHaveCount(0) await commands.getByRole('button', { name: '关闭', exact: true }).click() await page.getByRole('tab', { name: '对话', exact: true }).click() await page.screenshot({ path: '/tmp/fastppt-compact-workspace.png' }) diff --git a/tests/e2e/specs/managed-theme-dialogs.spec.ts b/tests/e2e/specs/managed-theme-dialogs.spec.ts new file mode 100644 index 00000000..3cdc35ab --- /dev/null +++ b/tests/e2e/specs/managed-theme-dialogs.spec.ts @@ -0,0 +1,114 @@ +import { expect, test, type Locator, type Page } from '@playwright/test' +import type { ThemeSummary } from '@fastppt/protocol' + +const themes: ThemeSummary[] = ['Academy', 'Landing'].map(name => ({ + themeId: `slidev-theme-${name.toLowerCase()}`, + packageName: `slidev-theme-${name.toLowerCase()}`, + displayName: name, + version: '0.1.0', + description: `${name} theme description`, + repositoryUrl: 'https://github.com/FastR-D/FastPPT', + skillId: `fastppt-theme-${name.toLowerCase()}`, + skillVersion: '0.1.0', + layouts: [{ id: 'cover', label: '学术封面' }], + supportedFeatures: [{ id: 'citations', label: '引用', description: '保留学术引用' }], + registryVersion: 'ui-v1', + available: true, +})) + +test.beforeEach(async ({ page }) => { + await page.route('**/api/v1/themes', route => route.fulfill({ json: themes })) + await page.route('**/api/v1/themes/*/skill', route => { + const theme = themes.find(theme => route.request().url().includes(theme.themeId))! + return route.fulfill({ json: { themeId: theme.themeId, skillId: theme.skillId, version: theme.skillVersion, fileName: 'SKILL.md', content: `# ${theme.displayName} skill source` } }) + }) + await page.route('**/api/v1/managed/status', route => route.fulfill({ json: { + registryVersion: 'ui-v1', + skills: [ + { harness: 'claude', skillId: 'fastppt-base', kind: 'base', expectedVersion: '0.1.0', state: 'installed', targetPath: '/fixture/base', managed: true }, + { harness: 'claude', skillId: 'fastppt-theme-academy', kind: 'theme', themeId: 'slidev-theme-academy', expectedVersion: '0.1.0', state: 'missing', targetPath: '/fixture/theme', managed: true, message: 'Theme installation required' }, + ], + mcp: [{ harness: 'claude', state: 'pending-trust', configPath: '/fixture/mcp', managed: true, message: 'Confirm MCP trust' }], + } })) +}) + +async function expectNativeFocus(page: Page, dialog: Locator, background: Locator) { + await expect(dialog).toBeVisible() + await expect.poll(() => dialog.evaluate(element => element instanceof HTMLDialogElement && element.matches(':modal'))).toBe(true) + await expect.poll(() => dialog.evaluate(element => element.contains(document.activeElement))).toBe(true) + await background.evaluate(element => (element as HTMLElement).focus()) + await expect(background).not.toBeFocused() + expect(await dialog.evaluate(element => element.contains(document.activeElement))).toBe(true) + await dialog.getByRole('heading').first().click() + await expect(dialog).toBeVisible() + await expect(page.locator('dialog[open]')).toHaveCount(1) +} + +for (const kind of ['status', 'details', 'skill'] as const) { + test(`owns native focus, inertness and every close path for ${kind}`, async ({ page }) => { + await page.goto('/') + if (kind !== 'status') await page.getByRole('tab', { name: '主题', exact: true }).click() + const trigger = kind === 'status' + ? page.getByRole('button', { name: '查看 Skill 与 MCP 安装状态', exact: true }) + : page.getByRole('button', { name: kind === 'details' ? '查看 Academy 详细功能' : '查看 fastppt-theme-academy 文件', exact: true }) + const dialog = page.getByRole('dialog', { name: kind === 'status' ? 'Skill 与 MCP 安装状态' : kind === 'details' ? 'Academy' : 'fastppt-theme-academy', exact: true }) + for (const action of ['escape', 'backdrop', 'button']) { + await trigger.click() + await expectNativeFocus(page, dialog, trigger) + if (kind === 'status') { + await expect(dialog).toContainText('1/2 个 Skill 已安装') + await expect(dialog).toContainText('Theme installation required') + await expect(dialog).toContainText('Confirm MCP trust') + } else if (kind === 'details') { + await expect(dialog).toContainText('保留学术引用') + await expect(dialog).toContainText('学术封面') + } else await expect(dialog).toContainText('# Academy skill source') + if (action === 'escape') await page.keyboard.press('Escape') + else if (action === 'button') await dialog.getByRole('button', { name: kind === 'status' ? '关闭' : '关闭弹窗', exact: true }).click() + else { + const box = (await dialog.boundingBox())! + await page.mouse.move(box.x - 8, box.y + box.height / 2) + await page.mouse.down() + await expect(dialog).toBeVisible() + await page.mouse.up() + } + await expect(dialog).toBeHidden() + await expect(page.locator('dialog[open]')).toHaveCount(0) + await expect(trigger).toBeFocused() + } + }) +} + +test('keeps a dismissed Skill request closed and preserves loading, error and source states', async ({ page }) => { + let release!: () => void + const held = new PromiseStraße
', 'Edited Straße
')) await expect(capitalization.locator('p').first()).toHaveJSProperty('innerText', 'Edited Straße') - await page.goto('http://127.0.0.1:4332/10') + await page.goto('http://localhost:4332/10') await expect(page.locator('[data-fastppt-layout-object="MCP Capitals"] > span')).toHaveCSS('font-variant-caps', 'small-caps') - await page.goto('http://127.0.0.1:4332/11') + await page.goto('http://localhost:4332/11') const rtl = page.locator('#standalone-rtl') await expect(rtl).toBeVisible() expect(await rtl.textContent()).toBe('مرحبا بالعالم 123 ABCשלום עולם 123 ABC') diff --git a/tests/e2e/specs/workbench.spec.ts b/tests/e2e/specs/workbench.spec.ts index f81874ae..08f33ec3 100644 --- a/tests/e2e/specs/workbench.spec.ts +++ b/tests/e2e/specs/workbench.spec.ts @@ -170,9 +170,11 @@ test('keeps session configuration keyboard focus inside the dialog', async ({ await expect(dialog).toBeVisible() await dialog.getByRole('button', { name: '关闭', exact: true }).focus() await page.keyboard.press('Shift+Tab') - await expect( - dialog.getByRole('button', { name: '取消', exact: true }), - ).toBeFocused() + // Native dialogs may focus the dialog surface at the traversal boundary. + await expect(trigger).not.toBeFocused() + await expect.poll(() => dialog.evaluate(element => element.contains(document.activeElement))).toBe(true) + await page.keyboard.press('Tab') + await expect.poll(() => dialog.evaluate(element => element.contains(document.activeElement))).toBe(true) await page.keyboard.press('Control+k') await expect(page.getByRole('dialog', { name: '创建向导' })).toHaveCount(0) await page.keyboard.press('Escape') @@ -180,6 +182,27 @@ test('keeps session configuration keyboard focus inside the dialog', async ({ await expect(trigger).toBeFocused() }) +test('keeps deck selector arrow keys native instead of turning preview pages', async ({ page }) => { + await page.goto('/') + const picker = page.getByRole('combobox', { name: 'Preview deck', exact: true }) + await expect(picker).toBeEnabled() + await picker.selectOption({ index: 0 }) + const first = await picker.inputValue() + await picker.evaluate(element => { + element.addEventListener('keydown', event => { + element.setAttribute('data-key-prevented', String(event.defaultPrevented)) + }) + }) + await picker.press('ArrowRight') + await expect(picker).not.toHaveValue(first) + await expect(picker).toHaveAttribute('data-key-prevented', 'false') + await expect(picker).toBeFocused() + const heights = await page.locator('.preview-identity select, .page-indicator input, .preview-actions > button, .preview-more > summary') + .evaluateAll(elements => elements.map(element => element.getBoundingClientRect().height)) + expect(heights.length).toBeGreaterThanOrEqual(6) + heights.forEach(height => expect(height).toBe(24)) +}) + test('uses the shared button and field styles throughout the creation wizard', async ({ page, }) => { @@ -193,13 +216,7 @@ test('uses the shared button and field styles throughout the creation wizard', a const wizard = page.getByRole('dialog', { name: '创建向导' }) const next = wizard.getByRole('button', { name: '保存并继续', exact: true }) await expect(next).toBeEnabled() - await expect(next).toHaveClass(/fp-button--primary/) - await expect(next).toHaveClass(/fp-button--md/) - await expect(wizard.getByRole('button', { name: '稍后继续' })).toHaveClass( - /fp-button--secondary/, - ) - await expect(wizard.getByLabel('推荐选项')).toHaveClass(/fp-select/) - await expect(wizard.getByLabel('本步备注')).toHaveClass(/fp-textarea/) + await expect(next).toHaveCSS('background-color', 'rgb(21, 95, 184)') await wizard.getByLabel('推荐选项').selectOption('从零创建') await wizard.getByLabel('本步备注').fill('组件库表单验证') await wizard.getByLabel('锁定本步决策').check() @@ -215,16 +232,16 @@ test('uses the shared button and field styles throughout the creation wizard', a test('switches color modes and restores the saved choice', async ({ page }) => { await page.emulateMedia({ colorScheme: 'light' }) await page.goto('/') - await expect(page.locator('html')).toHaveAttribute('data-theme', 'light') + await expect(page.locator('html')).toHaveAttribute('data-fastui-theme', 'light') await page.getByRole('button', { name: '切换到深色模式' }).click() - await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark') + await expect(page.locator('html')).toHaveAttribute('data-fastui-theme', 'dark') await expect( page.getByRole('button', { name: '切换到浅色模式' }), ).toHaveAttribute('aria-pressed', 'true') await page.reload() - await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark') + await expect(page.locator('html')).toHaveAttribute('data-fastui-theme', 'dark') await page.getByRole('button', { name: '切换到浅色模式' }).click() - await expect(page.locator('html')).toHaveAttribute('data-theme', 'light') + await expect(page.locator('html')).toHaveAttribute('data-fastui-theme', 'light') expect( await page.evaluate(() => localStorage.getItem('fastppt.color-mode')), ).toBe('light') @@ -235,13 +252,13 @@ test('follows the system color scheme until the user selects a mode', async ({ }) => { await page.emulateMedia({ colorScheme: 'dark' }) await page.goto('/') - await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark') + await expect(page.locator('html')).toHaveAttribute('data-fastui-theme', 'dark') await page.emulateMedia({ colorScheme: 'light' }) - await expect(page.locator('html')).toHaveAttribute('data-theme', 'light') + await expect(page.locator('html')).toHaveAttribute('data-fastui-theme', 'light') await page.getByRole('button', { name: '切换到深色模式' }).click() await page.emulateMedia({ colorScheme: 'dark' }) await page.emulateMedia({ colorScheme: 'light' }) - await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark') + await expect(page.locator('html')).toHaveAttribute('data-fastui-theme', 'dark') }) test('keeps the unsaved editor draft when changing color modes', async ({ diff --git a/themes/slidev-theme-academy/assets/logo.png b/themes/slidev-theme-academy/assets/logo.png new file mode 100644 index 00000000..54941a8a Binary files /dev/null and b/themes/slidev-theme-academy/assets/logo.png differ diff --git a/themes/slidev-theme-academy/public/ATLAS/ATLAS-Detector.webp b/themes/slidev-theme-academy/public/ATLAS/ATLAS-Detector.webp new file mode 100644 index 00000000..0b9a3b2d Binary files /dev/null and b/themes/slidev-theme-academy/public/ATLAS/ATLAS-Detector.webp differ diff --git a/themes/slidev-theme-academy/public/ATLAS/ATLAS-Logo.png b/themes/slidev-theme-academy/public/ATLAS/ATLAS-Logo.png new file mode 100644 index 00000000..5dbc4758 Binary files /dev/null and b/themes/slidev-theme-academy/public/ATLAS/ATLAS-Logo.png differ diff --git a/themes/slidev-theme-academy/public/videos/sample_video.mp4 b/themes/slidev-theme-academy/public/videos/sample_video.mp4 new file mode 100644 index 00000000..bf25b6c6 Binary files /dev/null and b/themes/slidev-theme-academy/public/videos/sample_video.mp4 differ diff --git a/themes/slidev-theme-landing/public/LAB.png b/themes/slidev-theme-landing/public/LAB.png new file mode 100644 index 00000000..a595376f Binary files /dev/null and b/themes/slidev-theme-landing/public/LAB.png differ diff --git a/themes/slidev-theme-landing/public/WHU.png b/themes/slidev-theme-landing/public/WHU.png new file mode 100644 index 00000000..ed02a648 Binary files /dev/null and b/themes/slidev-theme-landing/public/WHU.png differ diff --git a/themes/slidev-theme-nmt/public/images/ajg-designer.png b/themes/slidev-theme-nmt/public/images/ajg-designer.png new file mode 100644 index 00000000..73e5dd4a Binary files /dev/null and b/themes/slidev-theme-nmt/public/images/ajg-designer.png differ diff --git a/themes/slidev-theme-practicum/public/decor/decor-17.png b/themes/slidev-theme-practicum/public/decor/decor-17.png new file mode 100644 index 00000000..78e47e5f Binary files /dev/null and b/themes/slidev-theme-practicum/public/decor/decor-17.png differ diff --git a/themes/slidev-theme-practicum/public/decor/decor-18.png b/themes/slidev-theme-practicum/public/decor/decor-18.png new file mode 100644 index 00000000..3f6fa975 Binary files /dev/null and b/themes/slidev-theme-practicum/public/decor/decor-18.png differ diff --git a/themes/slidev-theme-practicum/public/decor/decor-2.png b/themes/slidev-theme-practicum/public/decor/decor-2.png new file mode 100644 index 00000000..3decd329 Binary files /dev/null and b/themes/slidev-theme-practicum/public/decor/decor-2.png differ diff --git a/themes/slidev-theme-practicum/public/decor/decor-5.png b/themes/slidev-theme-practicum/public/decor/decor-5.png new file mode 100644 index 00000000..cce0957d Binary files /dev/null and b/themes/slidev-theme-practicum/public/decor/decor-5.png differ diff --git a/themes/slidev-theme-practicum/public/decor/decor-6.png b/themes/slidev-theme-practicum/public/decor/decor-6.png new file mode 100644 index 00000000..c47e4251 Binary files /dev/null and b/themes/slidev-theme-practicum/public/decor/decor-6.png differ diff --git a/themes/slidev-theme-practicum/public/decor/decor-7.png b/themes/slidev-theme-practicum/public/decor/decor-7.png new file mode 100644 index 00000000..61785fcd Binary files /dev/null and b/themes/slidev-theme-practicum/public/decor/decor-7.png differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-1.webp b/themes/slidev-theme-practicum/public/photos/photo-1.webp new file mode 100644 index 00000000..99006f80 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-1.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-10.webp b/themes/slidev-theme-practicum/public/photos/photo-10.webp new file mode 100644 index 00000000..d8aec4fc Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-10.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-11.webp b/themes/slidev-theme-practicum/public/photos/photo-11.webp new file mode 100644 index 00000000..4990b635 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-11.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-12.webp b/themes/slidev-theme-practicum/public/photos/photo-12.webp new file mode 100644 index 00000000..7effc755 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-12.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-13.webp b/themes/slidev-theme-practicum/public/photos/photo-13.webp new file mode 100644 index 00000000..796341f5 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-13.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-14.webp b/themes/slidev-theme-practicum/public/photos/photo-14.webp new file mode 100644 index 00000000..86bebbdd Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-14.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-15.webp b/themes/slidev-theme-practicum/public/photos/photo-15.webp new file mode 100644 index 00000000..44404c73 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-15.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-16.webp b/themes/slidev-theme-practicum/public/photos/photo-16.webp new file mode 100644 index 00000000..77bca4c3 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-16.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-17.webp b/themes/slidev-theme-practicum/public/photos/photo-17.webp new file mode 100644 index 00000000..efcd644d Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-17.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-18.webp b/themes/slidev-theme-practicum/public/photos/photo-18.webp new file mode 100644 index 00000000..3500fb80 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-18.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-19.webp b/themes/slidev-theme-practicum/public/photos/photo-19.webp new file mode 100644 index 00000000..04bc0364 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-19.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-2.webp b/themes/slidev-theme-practicum/public/photos/photo-2.webp new file mode 100644 index 00000000..e40ad80d Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-2.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-20.webp b/themes/slidev-theme-practicum/public/photos/photo-20.webp new file mode 100644 index 00000000..4cf67d66 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-20.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-21.webp b/themes/slidev-theme-practicum/public/photos/photo-21.webp new file mode 100644 index 00000000..59915ba6 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-21.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-22.webp b/themes/slidev-theme-practicum/public/photos/photo-22.webp new file mode 100644 index 00000000..aedf975f Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-22.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-23.webp b/themes/slidev-theme-practicum/public/photos/photo-23.webp new file mode 100644 index 00000000..ae712b29 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-23.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-24.webp b/themes/slidev-theme-practicum/public/photos/photo-24.webp new file mode 100644 index 00000000..396d8773 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-24.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-25.webp b/themes/slidev-theme-practicum/public/photos/photo-25.webp new file mode 100644 index 00000000..13416241 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-25.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-26.webp b/themes/slidev-theme-practicum/public/photos/photo-26.webp new file mode 100644 index 00000000..b4fd2a13 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-26.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-27.webp b/themes/slidev-theme-practicum/public/photos/photo-27.webp new file mode 100644 index 00000000..105f225f Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-27.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-28.webp b/themes/slidev-theme-practicum/public/photos/photo-28.webp new file mode 100644 index 00000000..ffb36ed4 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-28.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-29.webp b/themes/slidev-theme-practicum/public/photos/photo-29.webp new file mode 100644 index 00000000..9ad1d2e4 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-29.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-3.webp b/themes/slidev-theme-practicum/public/photos/photo-3.webp new file mode 100644 index 00000000..cf1b2760 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-3.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-30.webp b/themes/slidev-theme-practicum/public/photos/photo-30.webp new file mode 100644 index 00000000..ccb094a4 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-30.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-4.webp b/themes/slidev-theme-practicum/public/photos/photo-4.webp new file mode 100644 index 00000000..1adbf5b8 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-4.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-5.webp b/themes/slidev-theme-practicum/public/photos/photo-5.webp new file mode 100644 index 00000000..487383e2 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-5.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-6.webp b/themes/slidev-theme-practicum/public/photos/photo-6.webp new file mode 100644 index 00000000..4e136d92 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-6.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-7.webp b/themes/slidev-theme-practicum/public/photos/photo-7.webp new file mode 100644 index 00000000..7e224682 Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-7.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-8.webp b/themes/slidev-theme-practicum/public/photos/photo-8.webp new file mode 100644 index 00000000..b615f00c Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-8.webp differ diff --git a/themes/slidev-theme-practicum/public/photos/photo-9.webp b/themes/slidev-theme-practicum/public/photos/photo-9.webp new file mode 100644 index 00000000..39d76d0e Binary files /dev/null and b/themes/slidev-theme-practicum/public/photos/photo-9.webp differ diff --git a/themes/slidev-theme-tud-db/assets/fonts/FiraCode-VariableFont_wght.ttf b/themes/slidev-theme-tud-db/assets/fonts/FiraCode-VariableFont_wght.ttf new file mode 100644 index 00000000..d7077f1d Binary files /dev/null and b/themes/slidev-theme-tud-db/assets/fonts/FiraCode-VariableFont_wght.ttf differ diff --git a/themes/slidev-theme-tud-db/assets/fonts/NotoSans-Italic-VariableFont_wdth,wght.ttf b/themes/slidev-theme-tud-db/assets/fonts/NotoSans-Italic-VariableFont_wdth,wght.ttf new file mode 100644 index 00000000..6245ba01 Binary files /dev/null and b/themes/slidev-theme-tud-db/assets/fonts/NotoSans-Italic-VariableFont_wdth,wght.ttf differ diff --git a/themes/slidev-theme-tud-db/assets/fonts/NotoSans-VariableFont_wdth,wght.ttf b/themes/slidev-theme-tud-db/assets/fonts/NotoSans-VariableFont_wdth,wght.ttf new file mode 100644 index 00000000..9530d84d Binary files /dev/null and b/themes/slidev-theme-tud-db/assets/fonts/NotoSans-VariableFont_wdth,wght.ttf differ