cdnV2

git clone https://github.com/notamitgamer/cdnV2.git
· rebuilt hourlyOpen on GitHub
commit 93f04e70f6967d1288db48a8e151f74dea46521d
parent 6cb17afc711cbede5453e1f053df275c9946cd3b
Author: claude-cdn-bot <claude-bot@users.noreply.github.com>
Date:   Tue, 13 Oct 2026 07:25:00 +0000

feat(ui): faster navigation - rearrange controls, no features removed

Placement only; everything that existed is still there. If this isn't an improvement, revert this one commit.

- Search box moved above the toolbar (filtering is the first thing people do)
- Toolbar is one compact row on phones; sort shows a sort icon + short labels; zip button shortens to '.zip'
- Find and Upload are one tap away in the phone header (no hamburger needed); not repeated in the menu panel
- Root: smaller heading and a compact, dismissible sync notice (remembered, applied before first paint)
- File page: primary 'Copy link' (raw URL) above the preview; Download and Open raw stay beside it
- 22 new browser checks (62 total); axe-core still 0 violations

Phone, 390x780: first folder on / 558px -> 403px, first file in a folder 366px -> 277px, file-page copy button 615px -> 215px.

Diffstat:
Mapp/static/app.css | 51++++++++++++++++++++++++++++++++++++++++-----------
Mapp/static/app.js | 8+++++++-
Mapp/templates/_icons.html | 1+
Mapp/templates/_listing.html | 20+++++++++++---------
Mapp/templates/_navbar.html | 6++++--
Mapp/templates/_page_batch.html | 2+-
Mapp/templates/_page_file.html | 3++-
Mapp/templates/_page_listing.html | 11+++++------
Mapp/templates/base.html | 1+
9 files changed, 72 insertions(+), 31 deletions(-)

diff --git a/app/static/app.css b/app/static/app.css @@ -242,8 +242,8 @@ body.turbo-loading .page { opacity: 0.55; pointer-events: none; transition: opac .page-head h1 { margin-bottom: 0.4rem; } .lede { color: var(--muted); font-size: var(--fs-lg); max-width: 62ch; margin: 0; text-wrap: pretty; } .section-title { font-size: var(--fs-lg); font-weight: 650; } -.hero { margin: 0.5rem 0 1.75rem; } -.hero h1 { margin-bottom: 1rem; max-width: 22ch; } +.hero { margin: 0 0 1rem; } +.hero h1 { margin-bottom: 0.75rem; font-size: clamp(1.5rem, 1.1rem + 1.6vw, 2rem); } /* ---- Header ---- */ .site-header { @@ -330,6 +330,10 @@ body.turbo-loading .page { opacity: 0.55; pointer-events: none; transition: opac :root[data-theme="dark"] .theme-toggle .i-sun { display: inline-flex; } :root[data-theme="dark"] .theme-toggle .i-moon { display: none; } .nav-toggle { display: none; } +.site-header__tools .quick-action { display: none; } +.quick-action.is-active { color: var(--accent); background: var(--accent-soft); } +.quick-action--primary { color: var(--accent); } +html[data-notice="off"] .site-notice { display: none; } .nav-toggle .i-close { display: none; } .nav-toggle[aria-expanded="true"] .i-open { display: none; } .nav-toggle[aria-expanded="true"] .i-close { display: inline-flex; } @@ -482,7 +486,9 @@ body.turbo-loading .page { opacity: 0.55; pointer-events: none; transition: opac .select { width: auto; min-height: 2.5rem; padding-right: 2rem; cursor: pointer; appearance: none; -webkit-appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 0.75rem center; } -.select-wrap { display: inline-flex; } +.select-wrap { position: relative; display: inline-flex; align-items: center; } +.select-wrap > svg { position: absolute; left: 0.7rem; color: var(--muted); pointer-events: none; } +.listing-toolbar .select { padding-left: 2rem; padding-right: 1.8rem; background-position: right 0.6rem center; } .input-group { position: relative; display: flex; align-items: center; width: 100%; } .input-group__icon { position: absolute; left: 1rem; display: inline-flex; color: var(--muted); pointer-events: none; } @@ -534,7 +540,17 @@ body.turbo-loading .page { opacity: 0.55; pointer-events: none; transition: opac .callout-warning > svg { color: var(--warning); } .callout a { font-weight: 550; } .alert-danger { background: var(--danger-soft); border-color: var(--danger-border); color: var(--danger); } -.site-notice { color: var(--muted); } +.site-notice { + display: flex; align-items: flex-start; gap: 0.6rem; + margin: 0 0 1rem; padding: 0.55rem 0.4rem 0.55rem 0.85rem; + background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); + color: var(--muted); font-size: var(--fs-sm); line-height: 1.5; +} +.site-notice > svg { flex: none; margin-top: 0.2rem; color: var(--accent); } +.site-notice > span { flex: 1; min-width: 0; } +.site-notice a { font-weight: 550; } +.notice-close { flex: none; display: grid; place-items: center; width: 2rem; height: 2rem; margin: -0.2rem 0 -0.2rem 0; padding: 0; background: none; border: 0; border-radius: var(--r-sm); color: var(--muted); cursor: pointer; } +.notice-close:hover { background: var(--surface-3); color: var(--text); } .toasts { position: fixed; z-index: 400; @@ -580,8 +596,14 @@ body.turbo-loading .page { opacity: 0.55; pointer-events: none; transition: opac /* ============================== 6. Lists ============================== */ .listing-toolbar { display: flex; align-items: center; justify-content: space-between; - flex-wrap: wrap; gap: 0.75rem 1rem; margin-bottom: 1rem; + flex-wrap: wrap; gap: 0.5rem 0.75rem; margin-bottom: 0.75rem; } +.listing-toolbar .select { min-height: 2.25rem; padding-top: 0.3rem; padding-bottom: 0.3rem; font-size: var(--fs-sm); border-color: var(--border); box-shadow: none; } +.listing-toolbar .btn { min-height: 2.25rem; padding-top: 0.3rem; padding-bottom: 0.3rem; font-size: var(--fs-sm); } +.zip-short { display: none; } +.input-group--standalone { margin-bottom: 0.75rem; } +.input-group--standalone .filter-input { box-shadow: var(--shadow-sm); } +.input-group--standalone .filter-input:focus { border-color: var(--accent); box-shadow: var(--ring); } .listing-toolbar__counts { margin: 0; color: var(--muted); font-family: var(--mono); font-size: var(--fs-sm); } .listing-toolbar__counts strong { color: var(--text); } .listing-toolbar__actions { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; } @@ -879,7 +901,10 @@ body.turbo-loading .page { opacity: 0.55; pointer-events: none; transition: opac .nav-cta { margin: 0.4rem 0 0; justify-content: center; } .more__toggle { display: none; } .more__menu { display: flex; position: static; min-width: 0; padding: 0; flex-direction: column; gap: 2px; background: none; border: 0; box-shadow: none; } - .site-header__tools { margin-left: auto; } + .site-header__tools { margin-left: auto; gap: 0.15rem; } + .site-header__tools .quick-action { display: inline-flex; } + .site-header__tools .icon-btn { width: 2.6rem; height: 2.6rem; } + .site-nav .nav-dup { display: none; } .wordmark { order: 0; } } @@ -888,7 +913,7 @@ body.turbo-loading .page { opacity: 0.55; pointer-events: none; transition: opac .page { padding-top: 1.1rem; } .file-head { grid-template-columns: auto minmax(0, 1fr); } .file-head__actions { grid-column: 1 / -1; justify-content: stretch; } - .file-head__actions .btn { flex: 1; } + .file-head__actions .btn { flex: 1 1 0; } .ft-badge { width: 3.2rem; height: 3.2rem; border-radius: 13px; font-size: 0.7rem; } .link-row { grid-template-columns: minmax(0, 1fr) auto; } .link-row__label { grid-column: 1 / -1; } @@ -896,10 +921,14 @@ body.turbo-loading .page { opacity: 0.55; pointer-events: none; transition: opac .toc ol { columns: 1; } .list-item { padding-left: 0.85rem; padding-right: 0.85rem; } .item-name { gap: 0.65rem; } - .listing-toolbar__actions { width: 100%; } - .listing-toolbar__actions .btn { flex: 1 0 auto; white-space: nowrap; } - .listing-toolbar__actions .select-wrap { flex: 0 1 9.5rem; min-width: 0; } - .listing-toolbar__actions .select { width: 100%; } + .listing-toolbar { flex-wrap: nowrap; } + .listing-toolbar__counts { flex: 1 1 auto; min-width: 0; line-height: 1.35; } + .listing-toolbar__actions { flex: 0 0 auto; flex-wrap: nowrap; gap: 0.4rem; } + .listing-toolbar__actions .btn { white-space: nowrap; padding-left: 0.7rem; padding-right: 0.7rem; } + .zip-long { display: none; } + .zip-short { display: inline; } + .listing-toolbar__actions .select { max-width: 7.6rem; } + .file-head__actions .btn-primary { flex: 1 1 100%; } .dropzone { padding: 2.25rem 1rem; } .input-row { flex-direction: column; } .page-stats .tiles { grid-template-columns: repeat(2, 1fr); } diff --git a/app/static/app.js b/app/static/app.js @@ -190,6 +190,11 @@ // an <a data-action="dialog-close"> still follows its href (the zip download) return; } + if (action === 'dismiss-notice') { + store.set('cdn_notice_off_v1', '1'); + document.documentElement.setAttribute('data-notice', 'off'); + return; + } if (action === 'clear-history') { store.del(act.getAttribute('data-key')); renderHistory(); @@ -355,6 +360,7 @@ const downloadUrl = batchId ? '/api/pack-batch/' + batchId : '/api/pack/' + path; const spinner = $('#zip-spinner'); const label = $('#zip-btn-text'); + const labelHtml = label ? label.innerHTML : ''; btn.disabled = true; if (spinner) spinner.style.display = 'block'; if (label) label.textContent = 'Calculating…'; @@ -373,7 +379,7 @@ } finally { btn.disabled = false; if (spinner) spinner.style.display = 'none'; - if (label) label.textContent = 'Download all (.zip)'; + if (label) label.innerHTML = labelHtml; } } function showErrorDialog(message) { diff --git a/app/templates/_icons.html b/app/templates/_icons.html @@ -24,6 +24,7 @@ 'chevron-down': '<polyline points="6 9 12 15 18 9"/>', 'chevron-left': '<polyline points="15 18 9 12 15 6"/>', 'arrow-up': '<line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/>', + 'sort': '<path d="M7 4v16M7 4L4 7M7 4l3 3"/><path d="M17 20V4M17 20l-3-3M17 20l3-3"/>', 'loader': '<path d="M21 12a9 9 0 1 1-6.219-8.56"/>', } %} {% macro icon(name, size=16, width=2) -%} diff --git a/app/templates/_listing.html b/app/templates/_listing.html @@ -12,11 +12,12 @@ {% macro root_header() %} <section class="hero"> <h1>A small file shelf for <a href="https://amit.is-a.dev">my</a> projects.</h1> - <p class="callout site-notice"> - {{ icon('info', 18) }} + <p class="site-notice" role="note"> + {{ icon('info', 16) }} <span>Files here can now be synced straight from GitHub on every push — click <a href="/gh-sync">Sync</a> at the top to see how to set it up. Project assets live here; files and paths may change over time, so use direct links with that in mind.</span> + <button type="button" class="notice-close" data-action="dismiss-notice" aria-label="Dismiss this notice" title="Dismiss">{{ icon('close', 14, 2.5) }}</button> </p> </section> {% endmacro %} @@ -43,19 +44,20 @@ </p> <div class="listing-toolbar__actions"> <label class="select-wrap" title="Sort this folder"> + {{ icon('sort', 14) }} <span class="sr-only">Sort by</span> <select id="sort-select" class="select" data-action="sort"> - <option value="default">Default order</option> - <option value="name">Name (A–Z)</option> - <option value="name-desc">Name (Z–A)</option> - <option value="size-desc">Size (largest)</option> - <option value="size-asc">Size (smallest)</option> + <option value="default">Default</option> + <option value="name">Name A–Z</option> + <option value="name-desc">Name Z–A</option> + <option value="size-desc">Largest</option> + <option value="size-asc">Smallest</option> </select> </label> - <button id="download-zip-btn" type="button" class="btn" data-path="{{ path }}" title="Download everything in this folder as a .zip"> + <button id="download-zip-btn" type="button" class="btn" data-path="{{ path }}" title="Download everything in this folder as a .zip" aria-label="Download everything in this folder as a .zip"> <svg id="zip-spinner" class="spin-anim" style="display: none;" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg> {{ icon('download') }} - <span id="zip-btn-text">Download all (.zip)</span> + <span id="zip-btn-text"><span class="zip-long">Download all (.zip)</span><span class="zip-short">.zip</span></span> </button> </div> </div> diff --git a/app/templates/_navbar.html b/app/templates/_navbar.html @@ -12,6 +12,8 @@ </a> <div class="site-header__tools"> + <a href="/find" class="icon-btn quick-action{{ ' is-active' if page == 'search' }}" aria-label="Find a file" title="Find"{{ ' aria-current="page"'|safe if page == 'search' }}>{{ icon('search', 18) }}</a> + <a href="/new" class="icon-btn quick-action quick-action--primary{{ ' is-active' if page == 'upload' }}" aria-label="Upload" title="Upload"{{ ' aria-current="page"'|safe if page == 'upload' }}>{{ icon('upload', 18) }}</a> <button type="button" class="icon-btn theme-toggle" data-action="theme-toggle" aria-label="Switch colour theme" title="Switch colour theme"> <span class="i-sun">{{ icon('sun', 18) }}</span><span class="i-moon">{{ icon('moon', 18) }}</span> </button> @@ -21,7 +23,7 @@ </div> <nav class="site-nav" id="primary-nav" aria-label="Primary"> - {{ nav_link('/find', 'search', 'Find', 'search') }} + {{ nav_link('/find', 'search', 'Find', 'search', 'nav-dup') }} {{ nav_link('/shorten', 'shorten', 'Shorten', 'link') }} {{ nav_link('/gh-sync', 'gh-sync', 'Sync', 'sync') }} {{ nav_link('/documentation', 'documentation', 'Docs', 'book') }} @@ -35,7 +37,7 @@ <a href="/admin" class="nav-link">{{ icon('shield') }}<span>Admin</span></a> </div> </div> - {{ nav_link('/new', 'upload', 'Upload', 'upload', 'nav-cta') }} + {{ nav_link('/new', 'upload', 'Upload', 'upload', 'nav-cta nav-dup') }} </nav> </div> </header> diff --git a/app/templates/_page_batch.html b/app/templates/_page_batch.html @@ -11,7 +11,7 @@ <button id="download-zip-btn" type="button" class="btn btn-primary" data-batch-id="{{ batch_id }}" title="Download every file in this batch as a .zip"> <svg id="zip-spinner" class="spin-anim" style="display: none;" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg> {{ icon('download') }} - <span id="zip-btn-text">Download all (.zip)</span> + <span id="zip-btn-text"><span class="zip-long">Download all (.zip)</span><span class="zip-short">.zip</span></span> </button> </div> </div> diff --git a/app/templates/_page_file.html b/app/templates/_page_file.html @@ -39,7 +39,8 @@ </p> </div> <div class="file-head__actions"> - <a href="/api/get/{{ path | urlencode }}" class="btn btn-primary" data-turbo="false">{{ icon('download') }}<span>Download</span></a> + <button type="button" class="btn btn-primary" data-copy="{{ raw_url }}" title="Copy the raw link (for embedding)">{{ icon('copy') }}<span>Copy link</span></button> + <a href="/api/get/{{ path | urlencode }}" class="btn" data-turbo="false">{{ icon('download') }}<span>Download</span></a> <a href="{{ raw_url }}" class="btn" data-turbo="false" target="_blank" rel="noopener">{{ icon('external') }}<span>Open raw</span></a> </div> </header> diff --git a/app/templates/_page_listing.html b/app/templates/_page_listing.html @@ -12,17 +12,16 @@ {% set path_parts = path.split('/') %} {% if path_parts|length > 1 %}{% set parent_path = '/' + path_parts[:-1]|join('/') %}{% endif %} {% endif %} +<div class="input-group input-group--standalone" id="filter-group"> + <span class="input-group__icon">{{ icon('search', 16) }}</span> + <input type="text" id="filter-input" class="filter-input" placeholder="Search this folder... (press / to focus)" aria-label="Filter this folder" autocomplete="off" spellcheck="false"> + <button type="button" class="input-clear-btn" id="filter-clear" aria-label="Clear filter">{{ icon('close', 16, 2.5) }}</button> +</div> {% if skeleton %}<!--@toolbar-->{% elif items %} {{ toolbar(counts(dir_count, file_count), path) }} {% endif %} <div class="list-container"> <div class="list-group"> - <div class="input-group" id="filter-group"> - <span class="input-group__icon">{{ icon('search', 16) }}</span> - <input type="text" id="filter-input" class="filter-input" placeholder="Search this folder... (press / to focus)" aria-label="Filter this folder" autocomplete="off" spellcheck="false"> - <button type="button" class="input-clear-btn" id="filter-clear" aria-label="Clear filter">{{ icon('close', 16, 2.5) }}</button> - </div> - <div id="file-list"> {% if skeleton %}<!--@list-->{% else %} {% if path %} diff --git a/app/templates/base.html b/app/templates/base.html @@ -45,6 +45,7 @@ var t = localStorage.getItem('cdn_theme'); if (t !== 'light' && t !== 'dark') t = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; document.documentElement.setAttribute('data-theme', t); + if (localStorage.getItem('cdn_notice_off_v1') === '1') document.documentElement.setAttribute('data-notice', 'off'); } catch (e) {} })(); </script>