diff options
Diffstat (limited to 'src/index.php')
| -rw-r--r-- | src/index.php | 138 |
1 files changed, 128 insertions, 10 deletions
diff --git a/src/index.php b/src/index.php index 40bd103..65edf98 100644 --- a/src/index.php +++ b/src/index.php | |||
| @@ -7,7 +7,127 @@ | |||
| 7 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | 7 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 8 | <title>sent-web</title> | 8 | <title>sent-web</title> |
| 9 | <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kj-sh604/noir.css@latest/out/noir.min.css"> | 9 | <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kj-sh604/noir.css@latest/out/noir.min.css"> |
| 10 | <style> :root { --sent-fg: #000000; --sent-bg: #ffffff; --sent-font: 'Noto Color Emoji', 'DejaVu Sans', sans-serif; } body { max-width: 960px; margin: 0 auto; padding: 1rem; } .subtitle { opacity: 0.6; font-size: 0.9em; margin-top: -0.8em; } /* ── controls ── */ #controls { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; margin-bottom: 1rem; padding: 0.75rem; border: 1px solid currentColor; border-radius: 4px; } #controls label { display: flex; align-items: center; gap: 0.4rem; font-size: 0.9rem; } #controls input[type="color"] { width: 2rem; height: 2rem; padding: 0; border: 1px solid currentColor; cursor: pointer; background: none; } #controls select { max-width: 200px; } .upload-area { display: flex; align-items: center; gap: 0.5rem; } #upload-input { display: none; } #upload-status { font-size: 0.85rem; opacity: 0.7; } /* ── editor ── */ #input { width: 100%; min-height: 420px; font-family: monospace; font-size: 0.95rem; resize: vertical; tab-size: 4; } .btn-row { display: flex; gap: 0.5rem; margin-top: 0.5rem; } .hint { font-size: 0.8rem; opacity: 0.5; margin-top: 0.25rem; } /* ── presentation overlay ── */ #presentation { position: fixed; inset: 0; z-index: 9999; display: none; align-items: center; justify-content: flex-start; background: var(--sent-bg); color: var(--sent-fg); cursor: none; overflow: hidden; padding-left: 7.5%; } #presentation.active { display: flex; } #slide-content { text-align: left; white-space: pre-line; word-wrap: break-word; font-family: var(--sent-font); } #slide-content img { display: block; max-width: 85vw; max-height: 85vh; object-fit: contain; } </style> | 10 | <style> |
| 11 | :root { | ||
| 12 | --sent-fg: #000000; | ||
| 13 | --sent-bg: #ffffff; | ||
| 14 | --sent-font: 'Noto Color Emoji', 'DejaVu Sans', sans-serif; | ||
| 15 | } | ||
| 16 | |||
| 17 | body { | ||
| 18 | max-width: 960px; | ||
| 19 | margin: 0 auto; | ||
| 20 | padding: 1rem; | ||
| 21 | } | ||
| 22 | |||
| 23 | .subtitle { | ||
| 24 | opacity: 0.6; | ||
| 25 | font-size: 0.9em; | ||
| 26 | margin-top: -0.8em; | ||
| 27 | } | ||
| 28 | |||
| 29 | /* ── controls ── */ | ||
| 30 | #controls { | ||
| 31 | display: flex; | ||
| 32 | flex-wrap: wrap; | ||
| 33 | gap: 1rem; | ||
| 34 | align-items: center; | ||
| 35 | margin-bottom: 1rem; | ||
| 36 | padding: 0.75rem; | ||
| 37 | border: 1px solid currentColor; | ||
| 38 | border-radius: 4px; | ||
| 39 | } | ||
| 40 | |||
| 41 | #controls label { | ||
| 42 | display: flex; | ||
| 43 | align-items: center; | ||
| 44 | gap: 0.4rem; | ||
| 45 | font-size: 0.9rem; | ||
| 46 | } | ||
| 47 | |||
| 48 | #controls input[type="color"] { | ||
| 49 | width: 2rem; | ||
| 50 | height: 2rem; | ||
| 51 | padding: 0; | ||
| 52 | border: 1px solid currentColor; | ||
| 53 | cursor: pointer; | ||
| 54 | background: none; | ||
| 55 | } | ||
| 56 | |||
| 57 | #controls select { | ||
| 58 | max-width: 200px; | ||
| 59 | } | ||
| 60 | |||
| 61 | .upload-area { | ||
| 62 | display: flex; | ||
| 63 | align-items: center; | ||
| 64 | gap: 0.5rem; | ||
| 65 | } | ||
| 66 | |||
| 67 | #upload-input { | ||
| 68 | display: none; | ||
| 69 | } | ||
| 70 | |||
| 71 | #upload-status { | ||
| 72 | font-size: 0.85rem; | ||
| 73 | opacity: 0.7; | ||
| 74 | } | ||
| 75 | |||
| 76 | /* ── editor ── */ | ||
| 77 | #input { | ||
| 78 | width: 100%; | ||
| 79 | min-height: 420px; | ||
| 80 | font-family: monospace; | ||
| 81 | font-size: 0.95rem; | ||
| 82 | resize: vertical; | ||
| 83 | tab-size: 4; | ||
| 84 | } | ||
| 85 | |||
| 86 | .btn-row { | ||
| 87 | display: flex; | ||
| 88 | gap: 0.5rem; | ||
| 89 | margin-top: 0.5rem; | ||
| 90 | } | ||
| 91 | |||
| 92 | .hint { | ||
| 93 | font-size: 0.8rem; | ||
| 94 | opacity: 0.5; | ||
| 95 | margin-top: 0.25rem; | ||
| 96 | } | ||
| 97 | |||
| 98 | /* ── presentation overlay ── */ | ||
| 99 | #presentation { | ||
| 100 | position: fixed; | ||
| 101 | inset: 0; | ||
| 102 | z-index: 9999; | ||
| 103 | display: none; | ||
| 104 | align-items: center; | ||
| 105 | justify-content: flex-start; | ||
| 106 | background: var(--sent-bg); | ||
| 107 | color: var(--sent-fg); | ||
| 108 | cursor: none; | ||
| 109 | overflow: hidden; | ||
| 110 | padding-left: 7.5%; | ||
| 111 | } | ||
| 112 | |||
| 113 | #presentation.active { | ||
| 114 | display: flex; | ||
| 115 | } | ||
| 116 | |||
| 117 | #slide-content { | ||
| 118 | text-align: left; | ||
| 119 | white-space: pre-line; | ||
| 120 | word-wrap: break-word; | ||
| 121 | font-family: var(--sent-font); | ||
| 122 | } | ||
| 123 | |||
| 124 | #slide-content img { | ||
| 125 | display: block; | ||
| 126 | max-width: 85vw; | ||
| 127 | max-height: 85vh; | ||
| 128 | object-fit: contain; | ||
| 129 | } | ||
| 130 | </style> | ||
| 11 | </head> | 131 | </head> |
| 12 | 132 | ||
| 13 | <body> | 133 | <body> |
| @@ -92,7 +212,7 @@ questions?</textarea> | |||
| 92 | </div> | 212 | </div> |
| 93 | 213 | ||
| 94 | <script> | 214 | <script> |
| 95 | // sent-web — vanilla JS presentation engine | 215 | // sent-web — vanilla JS presentation engine |
| 96 | 216 | ||
| 97 | const App = { | 217 | const App = { |
| 98 | slides: [], | 218 | slides: [], |
| @@ -246,9 +366,6 @@ questions?</textarea> | |||
| 246 | 366 | ||
| 247 | // stop presentation whenever fullscreen is exited (covers browser- | 367 | // stop presentation whenever fullscreen is exited (covers browser- |
| 248 | // intercepted Escape that never reaches the keydown handler). | 368 | // intercepted Escape that never reaches the keydown handler). |
| 249 | // Re-render on enter so the slide is drawn with the settled fullscreen | ||
| 250 | // viewport dimensions (requestFullscreen is async, so the initial | ||
| 251 | // renderSlide() call may use pre-fullscreen vw/vh values). | ||
| 252 | document.addEventListener('fullscreenchange', () => { | 369 | document.addEventListener('fullscreenchange', () => { |
| 253 | if (!document.fullscreenElement && this.presenting) { | 370 | if (!document.fullscreenElement && this.presenting) { |
| 254 | this.stopPresentation(); | 371 | this.stopPresentation(); |
| @@ -378,16 +495,16 @@ questions?</textarea> | |||
| 378 | } | 495 | } |
| 379 | img.alt = slide.img; | 496 | img.alt = slide.img; |
| 380 | // changed to reflect the fullscreen viewport. | 497 | // changed to reflect the fullscreen viewport. |
| 381 | const pw = pres.offsetWidth || window.innerWidth; | 498 | const pw = pres.offsetWidth || window.innerWidth; |
| 382 | const ph = pres.offsetHeight || window.innerHeight; | 499 | const ph = pres.offsetHeight || window.innerHeight; |
| 383 | const maxW = Math.floor(pw * this.settings.usableWidth); | 500 | const maxW = Math.floor(pw * this.settings.usableWidth); |
| 384 | const maxH = Math.floor(ph * this.settings.usableHeight); | 501 | const maxH = Math.floor(ph * this.settings.usableHeight); |
| 385 | img.style.width = maxW + 'px'; | 502 | img.style.width = maxW + 'px'; |
| 386 | img.style.height = maxH + 'px'; | 503 | img.style.height = maxH + 'px'; |
| 387 | img.style.maxWidth = maxW + 'px'; | 504 | img.style.maxWidth = maxW + 'px'; |
| 388 | img.style.maxHeight = maxH + 'px'; | 505 | img.style.maxHeight = maxH + 'px'; |
| 389 | img.style.objectFit = 'contain'; | 506 | img.style.objectFit = 'contain'; |
| 390 | img.style.display = 'block'; | 507 | img.style.display = 'block'; |
| 391 | content.appendChild(img); | 508 | content.appendChild(img); |
| 392 | } else { | 509 | } else { |
| 393 | // restore text-layout defaults | 510 | // restore text-layout defaults |
| @@ -709,4 +826,5 @@ questions?</textarea> | |||
| 709 | document.addEventListener('DOMContentLoaded', () => App.init()); | 826 | document.addEventListener('DOMContentLoaded', () => App.init()); |
| 710 | </script> | 827 | </script> |
| 711 | </body> | 828 | </body> |
| 829 | |||
| 712 | </html> \ No newline at end of file | 830 | </html> \ No newline at end of file |
