Last active
December 1, 2025 12:58
-
-
Save oeway/6b99a802ed7778da79e9560f0c207c8e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!doctype html> | |
| <html> | |
| <head> | |
| <title>Todo list</title> | |
| <style> | |
| body { | |
| margin: 0; | |
| font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | |
| background: radial-gradient(circle at top left, #1e293b, #020617); | |
| color: #e5e7eb; | |
| min-height: 100vh; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .app { | |
| width: 100%; | |
| max-width: 480px; | |
| background: rgba(15, 23, 42, 0.92); | |
| border-radius: 18px; | |
| box-shadow: 0 24px 80px rgba(0,0,0,0.65); | |
| border: 1px solid rgba(148, 163, 184, 0.35); | |
| overflow: hidden; | |
| backdrop-filter: blur(18px); | |
| } | |
| .app-header { | |
| padding: 18px 22px 14px; | |
| border-bottom: 1px solid rgba(51, 65, 85, 0.9); | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| background: linear-gradient(135deg, rgba(59,130,246,0.2), rgba(236,72,153,0.08)); | |
| } | |
| .title { | |
| font-size: 20px; | |
| font-weight: 650; | |
| letter-spacing: 0.02em; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .title-pill { | |
| font-size: 11px; | |
| text-transform: uppercase; | |
| padding: 2px 8px; | |
| border-radius: 999px; | |
| background: rgba(15,23,42,0.85); | |
| border: 1px solid rgba(148,163,184,0.5); | |
| color: #e5e7eb; | |
| } | |
| .glow-dot { | |
| width: 9px; | |
| height: 9px; | |
| border-radius: 999px; | |
| background: #22c55e; | |
| box-shadow: 0 0 12px rgba(34,197,94,0.9); | |
| } | |
| .app-body { | |
| padding: 18px 18px 16px; | |
| } | |
| .input-row { | |
| display: flex; | |
| gap: 10px; | |
| margin-bottom: 14px; | |
| } | |
| #new-todo { | |
| flex: 1; | |
| padding: 9px 11px; | |
| border-radius: 999px; | |
| border: 1px solid rgba(51,65,85,0.9); | |
| background: rgba(15,23,42,0.9); | |
| color: #e5e7eb; | |
| font-size: 13px; | |
| outline: none; | |
| } | |
| #new-todo::placeholder { | |
| color: #6b7280; | |
| } | |
| #add-btn { | |
| padding: 8px 14px; | |
| border-radius: 999px; | |
| border: none; | |
| background: linear-gradient(135deg,#3b82f6,#6366f1); | |
| color: white; | |
| font-size: 13px; | |
| font-weight: 550; | |
| cursor: pointer; | |
| box-shadow: 0 10px 25px rgba(37,99,235,0.45); | |
| transition: transform 0.08s ease-out, box-shadow 0.08s ease-out, filter 0.1s; | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| #add-btn:hover { | |
| transform: translateY(-1px); | |
| filter: brightness(1.05); | |
| box-shadow: 0 14px 32px rgba(37,99,235,0.6); | |
| } | |
| #add-btn:active { | |
| transform: translateY(0px) scale(0.98); | |
| box-shadow: 0 6px 18px rgba(15,23,42,0.9); | |
| } | |
| #todo-list { | |
| list-style: none; | |
| padding: 0; | |
| margin: 6px 0 0; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 6px; | |
| } | |
| .todo-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 8px 10px; | |
| border-radius: 12px; | |
| background: radial-gradient(circle at top left, rgba(30,64,175,0.55), rgba(15,23,42,0.98)); | |
| border: 1px solid rgba(51,65,85,0.95); | |
| box-shadow: 0 10px 28px rgba(15,23,42,0.95); | |
| font-size: 13px; | |
| } | |
| .todo-item span.text { | |
| flex: 1; | |
| } | |
| .todo-item button { | |
| background: transparent; | |
| border: none; | |
| color: #9ca3af; | |
| cursor: pointer; | |
| padding: 2px 4px; | |
| border-radius: 999px; | |
| font-size: 11px; | |
| } | |
| .todo-item button:hover { | |
| background: rgba(148,163,184,0.16); | |
| color: #e5e7eb; | |
| } | |
| .checkbox { | |
| width: 16px; | |
| height: 16px; | |
| border-radius: 999px; | |
| border: 1px solid rgba(148,163,184,0.85); | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| font-size: 11px; | |
| color: #22c55e; | |
| background: radial-gradient(circle at top left, rgba(15,23,42,1), rgba(15,23,42,0.4)); | |
| } | |
| .checkbox.checked { | |
| background: radial-gradient(circle at top left, rgba(34,197,94,0.85), rgba(22,163,74,0.9)); | |
| border-color: rgba(187,247,208,0.9); | |
| color: #022c22; | |
| box-shadow: 0 0 12px rgba(34,197,94,0.85); | |
| } | |
| .todo-item.completed span.text { | |
| text-decoration: line-through; | |
| color: #6b7280; | |
| } | |
| .meta-row { | |
| margin-top: 10px; | |
| font-size: 11px; | |
| display: flex; | |
| justify-content: space-between; | |
| color: #9ca3af; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="app"> | |
| <div class="app-header"> | |
| <div class="title"> | |
| <div class="glow-dot"></div> | |
| Todo list | |
| <span class="title-pill">Hypha</span> | |
| </div> | |
| <div style="font-size:11px;color:#cbd5f5;opacity:0.9;">RPC-ready</div> | |
| </div> | |
| <div class="app-body"> | |
| <div class="input-row"> | |
| <input id="new-todo" placeholder="Add a task and hit Enter" /> | |
| <button id="add-btn"> | |
| <span>+ Add</span> | |
| </button> | |
| </div> | |
| <ul id="todo-list"> | |
| <li class="todo-item"><div class="checkbox"></div><span class="text">Learn Hypha RPC</span><button class="del">✕</button></li> | |
| <li class="todo-item"><div class="checkbox"></div><span class="text">Build awesome apps</span><button class="del">✕</button></li> | |
| </ul> | |
| <div class="meta-row"> | |
| <span id="count"></span> | |
| <span style="opacity:0.85;">Double-click a task to edit</span> | |
| </div> | |
| </div> | |
| </div> | |
| <script type="module"> | |
| import * as hyphaWebsocketClient from "https://cdn.jsdelivr.net/npm/[email protected]/dist/hypha-rpc-websocket.mjs"; | |
| const api = await hyphaWebsocketClient.setupLocalClient({}) | |
| console.log("Connected to Hypha Core", api); | |
| const listEl = document.getElementById("todo-list"); | |
| const inputEl = document.getElementById("new-todo"); | |
| const countEl = document.getElementById("count"); | |
| const addBtn = document.getElementById("add-btn"); | |
| function updateCount() { | |
| const total = listEl.querySelectorAll('.todo-item').length; | |
| const done = listEl.querySelectorAll('.todo-item.completed').length; | |
| countEl.textContent = `${total-done} open • ${done} done`; | |
| } | |
| function makeItem(text) { | |
| const li = document.createElement('li'); | |
| li.className = 'todo-item'; | |
| li.innerHTML = `<div class="checkbox"></div><span class="text"></span><button class="del">✕</button>`; | |
| li.querySelector('span.text').textContent = text; | |
| return li; | |
| } | |
| function wireItem(li) { | |
| const box = li.querySelector('.checkbox'); | |
| const textSpan = li.querySelector('span.text'); | |
| const delBtn = li.querySelector('.del'); | |
| box.addEventListener('click', () => { | |
| box.classList.toggle('checked'); | |
| li.classList.toggle('completed'); | |
| updateCount(); | |
| }); | |
| delBtn.addEventListener('click', () => { | |
| li.remove(); | |
| updateCount(); | |
| }); | |
| textSpan.addEventListener('dblclick', () => { | |
| const current = textSpan.textContent; | |
| const input = document.createElement('input'); | |
| input.value = current; | |
| input.style.flex = '1'; | |
| input.style.background = 'transparent'; | |
| input.style.border = 'none'; | |
| input.style.color = 'inherit'; | |
| input.style.outline = 'none'; | |
| li.replaceChild(input, textSpan); | |
| input.focus(); | |
| input.select(); | |
| const finish = () => { | |
| const newSpan = document.createElement('span'); | |
| newSpan.className = 'text'; | |
| newSpan.textContent = input.value || current; | |
| li.replaceChild(newSpan, input); | |
| textSpan.textContent = newSpan.textContent; | |
| wireItem(li); | |
| }; | |
| input.addEventListener('blur', finish, { once: true }); | |
| input.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter') { | |
| input.blur(); | |
| } | |
| }); | |
| }); | |
| } | |
| // Wire existing items | |
| listEl.querySelectorAll('.todo-item').forEach(wireItem); | |
| updateCount(); | |
| function addFromInput() { | |
| const value = inputEl.value.trim(); | |
| if (!value) return; | |
| const li = makeItem(value); | |
| listEl.appendChild(li); | |
| wireItem(li); | |
| inputEl.value = ''; | |
| updateCount(); | |
| } | |
| addBtn.addEventListener('click', addFromInput); | |
| inputEl.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter') addFromInput(); | |
| }); | |
| // Export services that the parent can call | |
| await api.export({ | |
| getTodos: async () => { | |
| const todos = []; | |
| listEl.querySelectorAll('.todo-item span.text').forEach(span => { | |
| todos.push(span.textContent); | |
| }); | |
| return todos; | |
| }, | |
| addTodo: async (todo) => { | |
| const li = makeItem(todo); | |
| listEl.appendChild(li); | |
| wireItem(li); | |
| updateCount(); | |
| return true; | |
| } | |
| }); | |
| console.log("Hypha Service Exported"); | |
| </script> | |
| </body> | |
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| src = ''' | |
| <!doctype html> | |
| <html> | |
| <head> | |
| <title>Todo list</title> | |
| </head> | |
| <body> | |
| <h1>Todo list</h1> | |
| <ul id="todo-list"> | |
| <li>Learn Hypha RPC</li> | |
| <li>Build awesome apps</li> | |
| </ul> | |
| <script type="module"> | |
| import * as hyphaWebsocketClient from "https://cdn.jsdelivr.net/npm/[email protected]/dist/hypha-rpc-websocket.mjs"; | |
| const api = await hyphaWebsocketClient.setupLocalClient({}) | |
| console.log("Connected to Hypha Core", api); | |
| // Export services that the parent can call | |
| await api.export({ | |
| getTodos: async () => { | |
| const todos = []; | |
| document.querySelectorAll("#todo-list li").forEach(li => { | |
| todos.push(li.textContent); | |
| }); | |
| return todos; | |
| }, | |
| addTodo: async (todo) => { | |
| const li = document.createElement("li"); | |
| li.textContent = todo; | |
| document.getElementById("todo-list").appendChild(li); | |
| return true; | |
| } | |
| }); | |
| console.log("Hypha Service Exported"); | |
| </script> | |
| </body> | |
| </html> | |
| ''' | |
| todo = await api.createWindow(src=src, name='My Todo List') | |
| await todo.addTodo('buy TV') | |
| print(await todo.getTodos()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment