diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..01172ab --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,4 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "ignorePatterns": ["dist/**"] +} diff --git a/bun.lock b/bun.lock index 836d6da..40b3c94 100644 --- a/bun.lock +++ b/bun.lock @@ -4,15 +4,64 @@ "workspaces": { "": { "name": "psslides", + "dependencies": { + "emmet-monaco-es": "^5.7.0", + "monaco-editor": "0.55.1", + }, "devDependencies": { + "oxlint": "^1.85.0", "typescript": "~6.0.2", "vite": "^8.3.0", }, }, }, "packages": { + "@emmetio/abbreviation": ["@emmetio/abbreviation@2.3.3", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA=="], + + "@emmetio/css-abbreviation": ["@emmetio/css-abbreviation@2.1.8", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw=="], + + "@emmetio/scanner": ["@emmetio/scanner@1.0.4", "", {}, "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA=="], + "@oxc-project/types": ["@oxc-project/types@0.151.0", "", {}, "sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA=="], + "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.85.0", "", { "os": "android", "cpu": "arm" }, "sha512-q2KO/Zso9UT+OMn0NF9ywn4E4t0MI3yxiDhNyhsQ7DyQJrC4FhFE4TXOi4bktFnOWXTMds8qZSbpv2XwRaNOBg=="], + + "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.85.0", "", { "os": "android", "cpu": "arm64" }, "sha512-SxLN3ALjoT9NNdvpjEevGeHvfzTAFrF0NBYB5tzK7/GtCKMze3j1e/m/X2ozqGj2U9hfGG/dg/OG8vpVK4PiDA=="], + + "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.85.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y/Sup/J4f0f9UGsSd/xyCNTeWL+gepO63GBdEDAfue9nBsnk9zMmnIXx1O6b1V8C90vB5nucYNZ0pbMXAp8zJA=="], + + "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.85.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ApOSNC04ynpDTwvBD+//0wyfODRSbEzvRoKpX8teffmc27z8AockwSNeMXGJXn5KP85eahDgR/2llICWLkzcnw=="], + + "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.85.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-bNrVrCOA/kHky3Tu79IXWXe5bhIgLXfUuUEDHlAGOHUk96MkvDZ1ecaQF19rwstrnaqfP1o9nBTqzIr9+ZHkUg=="], + + "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.85.0", "", { "os": "linux", "cpu": "arm" }, "sha512-NUrzOJ1s/EqsVvfn2L/1D8Wro2LPIZUbihL8kOJLh5fEdGEN3rdOGUYq3HwnUIL8sjpoP+4N6RaGrgmMJnaMPw=="], + + "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.85.0", "", { "os": "linux", "cpu": "arm" }, "sha512-UJXrAT3E/RWkEqXLIs2ehETja1qfgkPb+5gwLIIS+o/6cf+grHvoOXTa5997a/YNQfcJS0DRBTOfZt95cvOI1g=="], + + "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.85.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-lK40QLjI0HxigO7CjDDshEtfYIeiYS0020v5BHFPqN4uuQBQxd2K9LNom2dW15o9F1937quSCRVp4ZsVhdbYdg=="], + + "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.85.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-c2zbdBwGKreHXwRx3gWBuFGJxLhxgsg6YlZ+3H+RgRusU/UEV9jNwJ3HGYK+nRo0LvBa7mt6Kj86xoVotUo8cw=="], + + "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.85.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tlt/Hy8lZ97/lCPmCgw/B3k/mwh+BzaIPbPkldZEly7TwLmx0xe2CQcaW2g/rR0dOgS9JNGCZsMEqLhUNMGvaw=="], + + "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.85.0", "", { "os": "linux", "cpu": "none" }, "sha512-3tNR9Xey82X0zKuY1d8hJ6Rc9gwRDurmqGLnQZa5xqOXy8/YyiqFXjAtugkKLY82obOlpK1eSiDRlgcNPuxtIg=="], + + "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.85.0", "", { "os": "linux", "cpu": "none" }, "sha512-wbGRd5PqCcjkJFHhZuZ2OBSUQY9czlQsoA/cQQB9JK/L9mC5MQgGoKAh+xd8QjA5V+0D3j+Qd1lAWn1I8zlelA=="], + + "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.85.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-3Sn0kSrE4DPZCWV/8o+n4x3aFZxI9ulMnkYlwCbJ8eUVkwRK2IerohE/A/z3SNbCwoPFOCJmGE5Avrq0rrvdvQ=="], + + "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.85.0", "", { "os": "linux", "cpu": "x64" }, "sha512-JY2pxxYfB62bAGfejljVCqc44etItehPuAyaeSAdMuEMtwNA00ggMnS66lC1oIhos6oOXUkuU6mZ9bpFh3BqWg=="], + + "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.85.0", "", { "os": "linux", "cpu": "x64" }, "sha512-5k74vZ6qJBjBHEOlBk9B/iv68Yu0F1Afw/vvT2ar6OGCqEeXLaSjXz2n/IPCbhLG22UoKoYEJTzpYraRdcp6PA=="], + + "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.85.0", "", { "os": "none", "cpu": "arm64" }, "sha512-GbAl5qt5TCkPLXTaIISZJnugrcBhra6rodcXc9jYt620UtdsTt71NlNmJmm0frxzFpd54x/G+MkitEJA8I/BoA=="], + + "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.85.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-kjmws5MK0et2swk4ND85D7NVQyDHw162i6whtZDLUA/lo6FQyBZDcmMRCMcVZcNrAhIaftVb00x9ChGDOjjNJA=="], + + "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.85.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-eSsIJx9n4yxvOqYTZyPEMyEXRmE60XH7xGAU7i0Qbsn1lf6Za3CWJ9aRd82oSFKXaxhp+sA6/yMJVRIpLpna6A=="], + + "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.85.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pBebIPUpKKhWrhSMWhy8TdAZBewiXnfxmaAGxhzxM1068GagqFaTwgKlU6e+UyJ2sPR+VoHouhXuGJkQjsrDvA=="], + "@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.10", "", { "os": "android", "cpu": "arm" }, "sha512-bp9svZb+QurZeh+8H4BhrZkifEB0YBNvTVzNSJnJQkj4NrRwmQoDUCGP0vSN7PbvLeM7l1tK6GXL8mrTiH2myg=="], "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.10", "", { "os": "android", "cpu": "arm64" }, "sha512-wm6Dld3RXUAZ/gRWKyUy+4W1B5CB5UeFaOzsSWJWEdxZXHH8rCYiZ5dGe6oJmhsunAPWzL7FZV+VtvmN5Ye2eA=="], @@ -45,8 +94,16 @@ "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], + "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "dompurify": ["dompurify@3.2.7", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw=="], + + "emmet": ["emmet@2.4.11", "", { "dependencies": { "@emmetio/abbreviation": "^2.3.3", "@emmetio/css-abbreviation": "^2.1.8" } }, "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ=="], + + "emmet-monaco-es": ["emmet-monaco-es@5.7.0", "", { "dependencies": { "emmet": "^2.4.11" }, "peerDependencies": { "monaco-editor": ">=0.22.0" } }, "sha512-q8jtgsEWWaXHnAJ6HAcwMB7WHraRtf032TWpeOpN6SGLtwxr7hd6RgF3Kbj2rPxb6Q19fYVVafAFODYRYUS8NQ=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], @@ -75,8 +132,14 @@ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="], + "marked": ["marked@14.0.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ=="], + + "monaco-editor": ["monaco-editor@0.55.1", "", { "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" } }, "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A=="], + "nanoid": ["nanoid@3.3.19", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug=="], + "oxlint": ["oxlint@1.85.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.85.0", "@oxlint/binding-android-arm64": "1.85.0", "@oxlint/binding-darwin-arm64": "1.85.0", "@oxlint/binding-darwin-x64": "1.85.0", "@oxlint/binding-freebsd-x64": "1.85.0", "@oxlint/binding-linux-arm-gnueabihf": "1.85.0", "@oxlint/binding-linux-arm-musleabihf": "1.85.0", "@oxlint/binding-linux-arm64-gnu": "1.85.0", "@oxlint/binding-linux-arm64-musl": "1.85.0", "@oxlint/binding-linux-ppc64-gnu": "1.85.0", "@oxlint/binding-linux-riscv64-gnu": "1.85.0", "@oxlint/binding-linux-riscv64-musl": "1.85.0", "@oxlint/binding-linux-s390x-gnu": "1.85.0", "@oxlint/binding-linux-x64-gnu": "1.85.0", "@oxlint/binding-linux-x64-musl": "1.85.0", "@oxlint/binding-openharmony-arm64": "1.85.0", "@oxlint/binding-win32-arm64-msvc": "1.85.0", "@oxlint/binding-win32-ia32-msvc": "1.85.0", "@oxlint/binding-win32-x64-msvc": "1.85.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2001", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-bc26s97nuvPj1ViyPsqmKecVkUWFMEdtayO8MaQ6oiLfs1pj94cQlZZhrh4BPNlr9HQosjhIlwgZKsfcwmcNgg=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], diff --git a/index.html b/index.html index 4c39146..efb8e9d 100644 --- a/index.html +++ b/index.html @@ -1,13 +1,23 @@ - - - - - p/slides - - -
- - - + + + + + + p/slides + + + +
+ +
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/package.json b/package.json index 6e92b1c..3fcce16 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,17 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", + "lint": "oxlint", + "lint:fix": "oxlint --fix", "preview": "vite preview" }, "devDependencies": { + "oxlint": "^1.85.0", "typescript": "~6.0.2", "vite": "^8.3.0" + }, + "dependencies": { + "emmet-monaco-es": "^5.7.0", + "monaco-editor": "0.55.1" } } diff --git a/src/code-to-element.ts b/src/code-to-element.ts new file mode 100644 index 0000000..7c9bd6a --- /dev/null +++ b/src/code-to-element.ts @@ -0,0 +1,48 @@ +import { domToSvgDataUrl } from './dom-to-image' + +export interface CodeToElementOptions { + title?: string + allowScripts?: boolean + styles?: string[] +} + +export function codeToElement( + code: string, + options: CodeToElementOptions = {} +): HTMLIFrameElement { + const iframe = document.createElement('iframe') + const sandbox = new Set(['allow-same-origin']) + + if (options.allowScripts) { + sandbox.add('allow-scripts') + } + + iframe.title = options.title ?? 'Rendered code' + iframe.setAttribute('sandbox', Array.from(sandbox).join(' ')) + iframe.srcdoc = `${(options.styles ?? []).join('\n')}${code}` + + return iframe +} + +export async function createCodeThumbnail( + code: string, + options: CodeToElementOptions = {} +): Promise { + const iframe = codeToElement(code, { title: 'Code thumbnail', ...options }) + iframe.style.width = '640px' + iframe.style.height = '360px' + iframe.style.position = 'fixed' + iframe.style.left = '-10000px' + iframe.style.border = '0' + document.body.appendChild(iframe) + + await new Promise((resolve) => iframe.addEventListener('load', () => resolve(), { once: true })) + + const content = iframe.contentDocument?.body + const thumbnail = document.createElement('img') + if (content) { + thumbnail.src = domToSvgDataUrl(content, { width: 640, height: 360 }) + } + iframe.remove() + return thumbnail +} diff --git a/src/dom-to-image.ts b/src/dom-to-image.ts new file mode 100644 index 0000000..7557a33 --- /dev/null +++ b/src/dom-to-image.ts @@ -0,0 +1,92 @@ +export interface DomExportOptions { + width?: number + height?: number + scale?: number +} + +function getElementSize(element: Element) { + const rect = element.getBoundingClientRect() + return { + width: Math.max(1, Math.ceil(rect.width)), + height: Math.max(1, Math.ceil(rect.height)) + } +} + +function copyComputedStyles(source: Element, target: Element) { + const computedStyle = window.getComputedStyle(source) + const style = Array.from(computedStyle) + .map((property) => `${property}:${computedStyle.getPropertyValue(property)}`) + .join(';') + + target.setAttribute('style', style) +} + +function cloneWithStyles(source: Node, target: Node) { + if (source.nodeType === Node.ELEMENT_NODE && target.nodeType === Node.ELEMENT_NODE) { + copyComputedStyles(source as Element, target as Element) + } + + source.childNodes.forEach((child) => { + const clonedChild = child.cloneNode(false) + cloneWithStyles(child, clonedChild) + target.appendChild(clonedChild) + }) +} + +function createSvg(element: Element, options: DomExportOptions) { + const size = getElementSize(element) + const width = options.width ?? size.width + const height = options.height ?? size.height + const clone = element.cloneNode(false) as Element + cloneWithStyles(element, clone) + const content = new XMLSerializer().serializeToString(clone) + const svg = `${content}` + + return { svg, width, height } +} + +export function domToSvgDataUrl( + element: Element, + options: DomExportOptions = {} +): string { + const { svg } = createSvg(element, options) + return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}` +} + +export async function domToCanvas( + element: Element, + options: DomExportOptions = {} +): Promise { + const size = getElementSize(element) + const width = options.width ?? size.width + const height = options.height ?? size.height + const scale = options.scale ?? 1 + const svgDataUrl = domToSvgDataUrl(element, options) + const image = new Image() + + await new Promise((resolve, reject) => { + image.onload = () => resolve() + image.onerror = () => reject(new Error('Unable to render DOM export as an image')) + image.src = svgDataUrl + }) + + const canvas = document.createElement('canvas') + canvas.width = Math.max(1, Math.ceil(width * scale)) + canvas.height = Math.max(1, Math.ceil(height * scale)) + const context = canvas.getContext('2d') + + if (!context) { + throw new Error('Unable to create a canvas context') + } + + context.drawImage(image, 0, 0, canvas.width, canvas.height) + return canvas +} + +export async function domToPngDataUrl( + element: Element, + options: DomExportOptions = {} +): Promise { + const canvas = await domToCanvas(element, options) + return canvas.toDataURL('image/png') +} diff --git a/src/editor.ts b/src/editor.ts new file mode 100644 index 0000000..7fa74f5 --- /dev/null +++ b/src/editor.ts @@ -0,0 +1,34 @@ +import * as monaco from 'monaco-editor' +import { emmetCSS, emmetHTML } from 'emmet-monaco-es' +import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker' +import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker' +import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker' + +emmetHTML(monaco, ['html']) +emmetCSS(monaco, ['css', 'scss', 'less']) + +self.MonacoEnvironment = { + getWorker(_, label) { + if (label === 'css' || label === 'scss' || label === 'less') { + return new cssWorker() + } + if (label === 'html' || label === 'handlebars' || label === 'razor') { + return new htmlWorker() + } + return new editorWorker() + } +} + +const MONACO_PARENT = '.code' + +export function createEditor() { + return monaco.editor.create( + document.querySelector(MONACO_PARENT)!, + { + value: 'test', + language: 'html', + theme: 'vs-light', + automaticLayout: true + } + ) +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 045bd6b..b7eeed2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,60 +1,84 @@ import './style.css' -import heroImg from './assets/hero.png' -import typescriptLogo from './assets/typescript.svg' -import viteLogo from './assets/vite.svg' -import { setupCounter } from './counter.ts' +import { createEditor } from './editor' +import { createCodeThumbnail } from './code-to-element' -document.querySelector('#app')!.innerHTML = ` -
-
- - TypeScript logo - Vite logo -
-
-

Get started

-

Edit src/main.ts and save to test HMR

-
- -
+interface Slide { + code: string +} -
+let globalSlide = '' -
-
- -

Documentation

-

Your questions, answered

- -
-
- -

Connect with us

-

Join the Vite community

- -
-
+const slides: Slide[] = [ + { + code: '

test

' + } +] -
-
-` +const editor = createEditor() +const slideList = document.querySelector('.slidelist')! +let currentSlide = 0 -setupCounter(document.querySelector('#counter')!) +function setSlide(index: number) { + const slide = index !== -1 ? slides[index] : { code: globalSlide } + if (!slide) { + return + } + + currentSlide = index + editor.getModel()?.setValue(slide.code) +} + +async function generateSlideThumbnail(slide: any, i: number) { + const slideInList = document.createElement('button') + slideInList.type = 'button' + slideInList.classList.add('slide-in-list') + slideInList.addEventListener('click', () => { + setSlide(i) + }) + slideInList.dataset.slideId = i.toString() + + + const thumbnail = await createCodeThumbnail(slide.code) + slideInList.appendChild(thumbnail) + if (i === -1) { + const p = document.createElement("p") + p.innerText = "(global)" + slideInList.appendChild(p) + } + + return slideInList +} + +async function setSlideList() { + const slideListSlidesList = [{ code: globalSlide }, ...slides] + + for (let i = -1; i < slides.length; i++) { + const slideInList = await generateSlideThumbnail(slideListSlidesList[i + 1], i) + slideList.appendChild(slideInList) + } +} + +editor.onDidChangeModelContent(async () => { + const code = editor.getValue() + + let slide = null + + if (currentSlide === -1) { + globalSlide = code + slide = { code } + } else { + slides[currentSlide].code = code + slide = slides[currentSlide] + } + + const slideEl = document.querySelector(`[data-slide-id="${currentSlide}"]`) + const slthumb = await generateSlideThumbnail(slide, currentSlide) + slideEl?.replaceWith(slthumb) +}) + +function render() { + setSlideList() + setSlide(currentSlide) +} + +render() \ No newline at end of file diff --git a/src/style.css b/src/style.css index 527d4fb..25b1c8c 100644 --- a/src/style.css +++ b/src/style.css @@ -1,296 +1,77 @@ :root { - --text: #6b6375; - --text-h: #08060d; - --bg: #fff; - --border: #e5e4e7; - --code-bg: #f4f3ec; - --accent: #aa3bff; - --accent-bg: rgba(170, 59, 255, 0.1); - --accent-border: rgba(170, 59, 255, 0.5); - --social-bg: rgba(244, 243, 236, 0.5); - --shadow: - rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px; - - --sans: system-ui, 'Segoe UI', Roboto, sans-serif; - --heading: system-ui, 'Segoe UI', Roboto, sans-serif; - --mono: ui-monospace, Consolas, monospace; - - font: 18px/145% var(--sans); - letter-spacing: 0.18px; - color-scheme: light dark; - color: var(--text); - background: var(--bg); - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - - @media (max-width: 1024px) { - font-size: 16px; - } -} - -@media (prefers-color-scheme: dark) { - :root { - --text: #9ca3af; - --text-h: #f3f4f6; - --bg: #16171d; - --border: #2e303a; - --code-bg: #1f2028; - --accent: #c084fc; - --accent-bg: rgba(192, 132, 252, 0.15); - --accent-border: rgba(192, 132, 252, 0.5); - --social-bg: rgba(47, 48, 58, 0.5); - --shadow: - rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px; - } - - #social .button-icon { - filter: invert(1) brightness(2); - } + --border-color: #3f3f3f } body { margin: 0; + font-family: sans-serif; } -h1, -h2 { - font-family: var(--heading); - font-weight: 500; - color: var(--text-h); +.container { + display: grid; + grid-template-columns: minmax(0, 0.6fr) minmax(0, 40%) minmax(0, 1fr); + grid-template-rows: 0.03fr 1fr; + gap: 0px 0px; + grid-auto-flow: row; + grid-template-areas: + "menubar menubar menubar" + "slidelist code preview"; + width: 100vw; + height: 100vh; } -h1 { - font-size: 56px; - letter-spacing: -1.68px; - margin: 32px 0; - @media (max-width: 1024px) { - font-size: 36px; - margin: 20px 0; - } -} -h2 { - font-size: 24px; - line-height: 118%; - letter-spacing: -0.24px; - margin: 0 0 8px; - @media (max-width: 1024px) { - font-size: 20px; - } -} -p { - margin: 0; -} - -code, -.counter { - font-family: var(--mono); - display: inline-flex; - border-radius: 4px; - color: var(--text-h); -} - -code { - font-size: 15px; - line-height: 135%; - padding: 4px 8px; - background: var(--code-bg); -} - -.counter { - font-size: 16px; - padding: 5px 10px; - border-radius: 5px; - color: var(--accent); - background: var(--accent-bg); - border: 2px solid transparent; - transition: border-color 0.3s; - margin-bottom: 24px; - - &:hover { - border-color: var(--accent-border); - } - &:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 2px; - } -} - -.hero { - position: relative; - - .base, - .framework, - .vite { - inset-inline: 0; - margin: 0 auto; - } - - .base { - width: 170px; - position: relative; - z-index: 0; - } - - .framework, - .vite { - position: absolute; - } - - .framework { - z-index: 1; - top: 34px; - height: 28px; - transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg) - scale(1.4); - } - - .vite { - z-index: 0; - top: 107px; - height: 26px; - width: auto; - transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg) - scale(0.8); - } -} - -#app { - width: 1126px; - max-width: 100%; - margin: 0 auto; - text-align: center; - border-inline: 1px solid var(--border); - min-height: 100svh; +.menubar { + grid-area: menubar; + background-color: #008800; + color: white; display: flex; - flex-direction: column; - box-sizing: border-box; + align-items: center; + padding-left: 1rem; + padding-right: 1rem; + border-bottom: 1px solid var(--border-color); } -#center { +.slidelist { + grid-area: slidelist; + min-width: 0; + border-right: 1px solid var(--border-color); + padding: 2rem; +} + +.code { + grid-area: code; + min-width: 0; + border-right: 1px solid var(--border-color); +} + +.preview { + grid-area: preview; + min-width: 0; display: flex; - flex-direction: column; - gap: 25px; - place-content: center; - place-items: center; - flex-grow: 1; - - @media (max-width: 1024px) { - padding: 32px 20px 24px; - gap: 18px; - } + align-items: center; + justify-content: center; + height: 100%; } -#next-steps { - display: flex; - border-top: 1px solid var(--border); - text-align: left; - - & > div { - flex: 1 1 0; - padding: 32px; - @media (max-width: 1024px) { - padding: 24px 20px; - } - } - - .icon { - margin-bottom: 16px; - width: 22px; - height: 22px; - } - - @media (max-width: 1024px) { - flex-direction: column; - text-align: center; - } -} - -#docs { - border-right: 1px solid var(--border); - - @media (max-width: 1024px) { - border-right: none; - border-bottom: 1px solid var(--border); - } -} - -#next-steps ul { - list-style: none; - padding: 0; - display: flex; - gap: 8px; - margin: 32px 0 0; - - .logo { - height: 18px; - } - - a { - color: var(--text-h); - font-size: 16px; - border-radius: 6px; - background: var(--social-bg); - display: flex; - padding: 6px 12px; - align-items: center; - gap: 8px; - text-decoration: none; - transition: box-shadow 0.3s; - - &:hover { - box-shadow: var(--shadow); - } - .button-icon { - height: 18px; - width: 18px; - } - } - - @media (max-width: 1024px) { - margin-top: 20px; - flex-wrap: wrap; - justify-content: center; - - li { - flex: 1 1 calc(50% - 8px); - } - - a { - width: 100%; - justify-content: center; - box-sizing: border-box; - } - } -} - -#spacer { - height: 88px; - border-top: 1px solid var(--border); - @media (max-width: 1024px) { - height: 48px; - } -} - -.ticks { - position: relative; +.slide { + display: block; width: 100%; - - &::before, - &::after { - content: ''; - position: absolute; - top: -4.5px; - border: 5px solid transparent; - } - - &::before { - left: 0; - border-left-color: var(--border); - } - &::after { - right: 0; - border-right-color: var(--border); - } + aspect-ratio: 16 / 9; + border: #000 solid 1px; } + +.slide-in-list { + display: block; + width: 100%; + border: 1px solid #000; + padding: 0; + margin-bottom: 1rem; + background: transparent; + cursor: pointer; +} + +.slide-in-list img { + display: block; + width: 100%; + height: auto; +} \ No newline at end of file