Something went wrong. Try again.
inkfinite (an infinite canvas/whiteboard) monorepo ink.stormlightlabs.org
svelte rust tauri svg canvas perfect-freehand
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455<script lang="ts"> import { resolve } from '$app/paths'; import { Icon } from '$ui';
let { id = 'docs-search' }: { id?: string } = $props();
const dialogId = $derived(`${id}-dialog`); const titleId = $derived(`${id}-title`); const descriptionId = $derived(`${id}-description`); const inputId = $derived(`${id}-input`); const resultsId = $derived(`${id}-results`);</script>
<div class="search" data-doc-search> <button class="search-trigger" type="button" data-search-trigger aria-controls={dialogId} aria-expanded="false" aria-haspopup="dialog" aria-label="Open documentation search (Ctrl+K or Cmd+K)"> <Icon name="search" size={17} /> <span class="search-trigger-label">Search docs</span> <span class="shortcut-hint" aria-hidden="true"> <kbd>Ctrl</kbd> <span>/</span> <kbd>⌘</kbd> <kbd>K</kbd> </span> </button>
<a class="search-fallback" href={resolve('/search/')}> <Icon name="book-open" size={17} /> <span>Browse documentation</span> </a>
<dialog class="command-dialog" data-search-dialog aria-labelledby={titleId} aria-describedby={descriptionId} id={dialogId}> <div class="dialog-inner"> <header class="dialog-header"> <div> <p class="dialog-kicker">Documentation search</p> <h2 id={titleId}>Search the docs</h2> </div> <button class="dialog-close" type="button" data-search-close aria-label="Close search"> <Icon name="close" size={18} /> </button> </header>
<p class="dialog-description" id={descriptionId}> Find drawing concepts, editor workflows, commands, and file-format details in the Inkfinite documentation. </p>
<form class="command-form" role="search" action={resolve('/search/')} method="get" data-search-form> <label class="sr-only" for={inputId}>Search documentation</label> <div class="command-input-wrap"> <Icon name="search" size={17} /> <input id={inputId} name="q" type="search" data-search-input placeholder="Search pages, commands, and concepts…" autocomplete="off" role="combobox" aria-autocomplete="list" aria-expanded="false" aria-haspopup="listbox" aria-controls={resultsId} /> <button type="submit" data-search-submit disabled aria-label="Search documentation"> <Icon name="arrow-right" size={17} /> </button> </div> </form>
<p class="search-status sr-only" data-search-status role="status" aria-live="polite"> </p> <div class="search-results" id={resultsId} data-search-results> <p class="search-message">Start typing to search the documentation.</p> </div>
<footer class="dialog-footer" aria-label="Search keyboard shortcuts"> <span><kbd>↑</kbd><kbd>↓</kbd> to navigate</span> <span><kbd>Enter</kbd> to open</span> <span><kbd>Esc</kbd> to close</span> </footer> </div> </dialog></div>
<style> .search { position: relative; min-width: 13rem; }
.search-trigger, .search-fallback { display: flex; align-items: center; width: 100%; min-height: 2.75rem; border: 1px solid var(--docs-border); border-radius: 0.35rem; background: var(--docs-surface-raised); color: var(--docs-text-muted); font: 550 0.86rem / 1 var(--docs-font-body); }
.search-trigger { gap: 0.5rem; padding: 0.42rem 0.55rem; text-align: left; cursor: pointer; }
.search-trigger:hover { border-color: var(--docs-accent); color: var(--docs-accent-text); }
.search-trigger:focus-visible { border-color: var(--docs-accent); }
.search-trigger-label { min-width: 0; overflow: hidden; color: var(--docs-text); font-weight: 550; text-overflow: ellipsis; white-space: nowrap; }
.shortcut-hint { display: inline-flex; align-items: center; gap: 0.18rem; margin-left: auto; color: var(--docs-text-muted); font-size: 0.67rem; line-height: 1; white-space: nowrap; }
.search kbd { padding: 0.18rem 0.3rem; border: 1px solid var(--docs-border); border-bottom-width: 2px; border-radius: 0.2rem; background: var(--docs-canvas); color: var(--docs-text-muted); font: 650 0.68rem / 1 var(--docs-font-mono); }
.search-fallback { gap: 0.5rem; padding: 0.42rem 0.55rem; text-decoration: none; }
.search-fallback:hover { border-color: var(--docs-accent); color: var(--docs-accent-text); }
.command-input-wrap button { display: inline-grid; place-items: center; width: 2.75rem; align-self: stretch; flex: 0 0 auto; border: 0; border-left: 1px solid var(--docs-border); background: transparent; color: var(--docs-accent-text); cursor: pointer; }
.command-input-wrap button:hover:not(:disabled) { background: var(--docs-surface-hover); }
.command-input-wrap button:disabled { cursor: not-allowed; opacity: 0.45; }
:global(html.js) .search-fallback { display: none; }
:global(html:not(.js)) .search-trigger { display: none; }
.command-dialog { width: min(42rem, calc(100vw - 2rem)); max-width: none; max-height: calc(100dvh - 2rem); margin: auto; padding: 0; overflow: hidden; border: 1px solid var(--docs-border); border-radius: 0.65rem; background: var(--docs-surface-raised); color: var(--docs-text); box-shadow: var(--docs-shadow); }
.command-dialog::backdrop { background: rgb(8 9 16 / 58%); backdrop-filter: blur(3px); }
.command-dialog[open] { animation: search-dialog-in 140ms ease-out; }
.dialog-inner { display: grid; gap: 1rem; max-height: calc(100dvh - 2rem); padding: 1.25rem; }
.dialog-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; }
.dialog-kicker { margin: 0 0 0.3rem; color: var(--docs-accent-text); font: 650 var(--docs-type-xs) / 1 var(--docs-font-mono); letter-spacing: 0.06em; text-transform: uppercase; }
.dialog-header h2 { margin: 0; color: var(--docs-heading); font-family: var(--docs-font-heading); font-size: clamp(1.45rem, 4vw, 1.9rem); line-height: 1.1; }
.dialog-close { display: inline-grid; place-items: center; width: 2.5rem; height: 2.5rem; flex: 0 0 auto; padding: 0; border: 1px solid var(--docs-border); border-radius: 0.3rem; background: transparent; color: var(--docs-text-muted); cursor: pointer; }
.dialog-close:hover { border-color: var(--docs-accent); background: var(--docs-surface-hover); color: var(--docs-accent-text); }
.dialog-description { max-width: 42rem; margin: -0.35rem 0 0; color: var(--docs-text-muted); font-size: 0.88rem; line-height: 1.45; }
.command-form { margin: 0; }
.command-input-wrap { display: flex; align-items: center; min-height: 3.2rem; border: 1px solid var(--docs-border); border-radius: 0.4rem; background: var(--docs-canvas); }
.command-input-wrap:focus-within { border-color: var(--docs-accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--docs-accent) 22%, transparent); }
.command-input-wrap > :global(.ink-icon) { margin-left: 1.1rem; flex: 0 0 auto; color: var(--docs-accent-text); }
.command-input-wrap input { width: 100%; min-width: 0; padding: 0.72rem 0.7rem; border: 0; outline: 0; background: transparent; color: var(--docs-text); font: inherit; font-size: 1rem; }
.search-results { min-height: 4rem; max-height: min(48vh, 24rem); margin: 0 -0.25rem; overflow-y: auto; overscroll-behavior: contain; }
.search-results :global(.search-result) { display: grid; gap: 0.18rem; margin: 0 0.25rem; padding: 0.75rem; border: 1px solid transparent; border-radius: 0.35rem; color: var(--docs-text); font-size: 0.86rem; text-decoration: none; }
.search-results :global(.search-result:hover), .search-results :global(.search-result[aria-selected='true']) { border-color: var(--docs-border); background: var(--docs-surface-hover); }
.search-results :global(.search-result strong) { color: var(--docs-heading); font-size: 0.95rem; font-weight: 650; }
.search-results :global(.search-result span) { display: block; color: var(--docs-text-muted); line-height: 1.45; }
.search-results :global(.search-result mark) { background: color-mix(in srgb, var(--docs-warning) 35%, transparent); color: var(--docs-text); }
.search-results :global(.search-message) { margin: 0; padding: 0.75rem; color: var(--docs-text-muted); font-size: 0.88rem; }
.dialog-footer { display: flex; flex-wrap: wrap; gap: 0.75rem 1rem; padding-top: 0.75rem; border-top: 1px solid var(--docs-border); color: var(--docs-text-muted); font-size: 0.72rem; }
.dialog-footer kbd { margin-top: 0.25rem; margin-right: 0.25rem; font-size: 0.625rem; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
@keyframes search-dialog-in { from { opacity: 0; transform: translateY(-0.5rem) scale(0.99); } to { opacity: 1; transform: translateY(0) scale(1); } }
@media (max-width: 900px) { .search { width: 100%; } }
@media (max-width: 520px) { .shortcut-hint { font-size: 0.62rem; }
.command-dialog { width: calc(100vw - 0.75rem); max-height: calc(100dvh - 0.75rem); border-radius: 0.5rem; }
.dialog-inner { max-height: calc(100dvh - 0.75rem); padding: 0.9rem; }
.dialog-footer { gap: 0.55rem 0.8rem; } }
@media (prefers-reduced-motion: reduce) { .command-dialog[open] { animation: none; } }</style>