From 838798ca9b0bce3e8973ce3d91e256e9db7e3d17 Mon Sep 17 00:00:00 2001 From: Jonathan Sykes Date: Sun, 4 Oct 2026 03:09:15 +0800 Subject: [PATCH] Build the new Bento Hub shell and browsing tiles --- frontend/app.js | 3 + frontend/bento-hub.js | 35 +++++ frontend/bento-hub.test.js | 12 ++ frontend/fonts/PlusJakartaSans-OFL.txt | 93 +++++++++++++ .../fonts/PlusJakartaSans-latin-ext.woff2 | Bin 0 -> 21728 bytes frontend/fonts/PlusJakartaSans-latin.woff2 | Bin 0 -> 27348 bytes frontend/glass-controls.js | 7 +- frontend/index.html | 2 + frontend/styles.css | 78 ----------- frontend/sw.js | 4 + frontend/theme-bento-hub.css | 130 ++++++++++++++++++ playwright.bento.config.js | 2 + tests/bento-hub.spec.js | 52 +++++++ 13 files changed, 337 insertions(+), 81 deletions(-) create mode 100644 frontend/bento-hub.js create mode 100644 frontend/bento-hub.test.js create mode 100644 frontend/fonts/PlusJakartaSans-OFL.txt create mode 100644 frontend/fonts/PlusJakartaSans-latin-ext.woff2 create mode 100644 frontend/fonts/PlusJakartaSans-latin.woff2 create mode 100644 frontend/theme-bento-hub.css create mode 100644 playwright.bento.config.js create mode 100644 tests/bento-hub.spec.js diff --git a/frontend/app.js b/frontend/app.js index 25ff7d4..82a86b2 100755 --- a/frontend/app.js +++ b/frontend/app.js @@ -8669,6 +8669,7 @@ function renderSidebar() { // the fallback so a first-time visitor still gets the landing. // Driven from renderSidebar() so every playlist mutation refreshes both. function renderHomePlaylists() { + if (window.BentoHub) BentoHub.playlists = () => data.playlists || []; const wrap = document.getElementById('homePlaylists'); const hero = document.querySelector('#playerPlaceholder .hero'); const grid = document.getElementById('homePlGrid'); @@ -8818,6 +8819,8 @@ function resetListHeaderExtras() { } function renderList() { + // Theme composition follows the real view without changing its controls. + els.cards.dataset.view = view.type; resetListHeaderExtras(); { const ls = $('listSub'); if (ls) ls.textContent = ''; const ps = $('p2pStrip'); if (ps) ps.classList.add('hidden'); } if (view.type !== 'search') { const sm = $('searchMeta'); if (sm) sm.classList.add('hidden'); } diff --git a/frontend/bento-hub.js b/frontend/bento-hub.js new file mode 100644 index 0000000..8de6fd5 --- /dev/null +++ b/frontend/bento-hub.js @@ -0,0 +1,35 @@ +/* Bento-only artwork and measured clearance. Navigation and player nodes stay put. */ +(function(root){ + 'use strict'; + function chromeClearance(height,boxes){ + const occupied=boxes.filter(b=>b.visible&&b.fixed&&b.height>0&&b.top>=height/2).map(b=>height-b.top); + return Math.max(0,...occupied)+16; + } + function mount(doc){ + let frame=0,active=false;const html=doc.documentElement; + const resize=new root.ResizeObserver(schedule),observer=new root.MutationObserver(schedule); + function paint(){ + frame=0;const on=html.dataset.layout==='bento-hub'; + if(!on){if(active){resize.disconnect();observer.disconnect();html.style.removeProperty('--bento-bottom-clearance');doc.querySelectorAll('.bento-collage').forEach(n=>n.remove());}active=false;return;} + if(!active){active=true;observer.observe(doc.body,{childList:true,subtree:true,attributes:true,attributeFilter:['class','hidden']});for(const id of ['bottomNav','miniBar','sectionRail']){const el=doc.getElementById(id);if(el)resize.observe(el);}} + const boxes=['bottomNav','miniBar','sectionRail'].map(id=>doc.getElementById(id)).filter(Boolean).map(el=>{const r=el.getBoundingClientRect(),s=root.getComputedStyle(el);return {top:r.top,height:r.height,fixed:s.position==='fixed',visible:s.display!=='none'&&s.visibility!=='hidden'};}); + const clearance=chromeClearance(root.innerHeight,boxes)+'px';if(html.style.getPropertyValue('--bento-bottom-clearance')!==clearance)html.style.setProperty('--bento-bottom-clearance',clearance); + for(const card of doc.querySelectorAll('.home-pl-card')){ + if(card.querySelector('.bento-collage'))continue; + // Use actual playlist artwork; never invent an image or a verification badge. + const name=card.querySelector('.home-pl-name')?.textContent; + const playlist=root.BentoHub.playlists?.().find(p=>p.name===name); + const images=(playlist?.videos||[]).filter(v=>v.thumbnail).slice(0,4); + if(images.length<4)continue; + const collage=doc.createElement('span');collage.className='bento-collage';collage.setAttribute('aria-hidden','true'); + for(const video of images){const img=doc.createElement('img');img.src=video.thumbnail;img.alt='';img.loading='lazy';collage.append(img);} + card.querySelector('.home-pl-thumb')?.prepend(collage); + } + } + function schedule(){if(!frame)frame=root.requestAnimationFrame(paint);} + new root.MutationObserver(schedule).observe(html,{attributes:true,attributeFilter:['data-layout']});root.addEventListener('resize',schedule);paint(); + } + const api={chromeClearance,playlists:null}; + if(typeof module!=='undefined')module.exports=api; + else {root.BentoHub=api; if(root.document.readyState==='loading')root.document.addEventListener('DOMContentLoaded',()=>mount(root.document),{once:true});else mount(root.document);} +})(typeof window==='undefined'?globalThis:window); diff --git a/frontend/bento-hub.test.js b/frontend/bento-hub.test.js new file mode 100644 index 0000000..17e15d3 --- /dev/null +++ b/frontend/bento-hub.test.js @@ -0,0 +1,12 @@ +const test=require('node:test'),assert=require('node:assert/strict'); +const {chromeClearance}=require('./bento-hub'); +test('clearance follows the highest visible floating control, including safe area',()=>{ + assert.equal(chromeClearance(844,[{visible:true,fixed:true,top:746,height:98},{visible:true,fixed:true,top:666,height:72},{visible:true,fixed:true,top:602,height:56}]),258); +}); +test('hidden and scrolling content cannot reserve fullscreen bottom space',()=>{ + assert.equal(chromeClearance(844,[{visible:false,fixed:true,top:602,height:56},{visible:true,fixed:false,top:500,height:100},{visible:true,fixed:true,top:0,height:64}]),16); +}); +test('desktop mini player reserves its real height and margin; no chrome is 16px',()=>{ + assert.equal(chromeClearance(844,[{visible:true,fixed:true,top:760,height:72}]),100); + assert.equal(chromeClearance(844,[]),16); +}); diff --git a/frontend/fonts/PlusJakartaSans-OFL.txt b/frontend/fonts/PlusJakartaSans-OFL.txt new file mode 100644 index 0000000..ea69b0c --- /dev/null +++ b/frontend/fonts/PlusJakartaSans-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Plus Jakarta Sans Project Authors (https://github.com/tokotype/PlusJakartaSans) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/frontend/fonts/PlusJakartaSans-latin-ext.woff2 b/frontend/fonts/PlusJakartaSans-latin-ext.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..f82597ce4a5f5478b12b9213cc446aabdae73b0d GIT binary patch literal 21728 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 H00000#1=#N literal 0 HcmV?d00001 diff --git a/frontend/fonts/PlusJakartaSans-latin.woff2 b/frontend/fonts/PlusJakartaSans-latin.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..a180dc40f205fc48979a75ccbe8a10221a71a4ba GIT binary patch literal 27348 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 +
@@ -678,6 +679,7 @@ + diff --git a/frontend/styles.css b/frontend/styles.css index fe1407b..613f9fa 100755 --- a/frontend/styles.css +++ b/frontend/styles.css @@ -3345,84 +3345,6 @@ html[data-layout="contemplative"] .brand-mark { box-shadow: 0 6px 18px -4px rgba(45, 212, 191, 0.4), inset 0 1px 0 rgba(255,255,255,0.25); } -/* --- Bento Hub ------------------------------------------------------------- - * GitHub-dark-ish with honey-gold accent. Tighter borders give a - * "dashboard card" feel throughout. - * ------------------------------------------------------------------------- */ -html[data-layout="bento-hub"] { - --bg: #0d1117; - --bg-1: #13181f; - --bg-2: #1c2333; - --bg-3: #252d3d; - --line: #30363d; - --line-soft: #21262d; - --text: #e6edf3; - --text-2: #8b949e; - --text-dim: #484f58; - --accent: #eab308; - --accent-bright: #fbbf24; - --accent-deep: #ca8a04; - --accent-glow: rgba(234, 179, 8, 0.4); - --radius: 10px; - --radius-sm: 6px; -} -html[data-layout="bento-hub"] body::before { - background: - radial-gradient(900px 600px at 18% -10%, rgba(234, 179, 8, 0.06), transparent 60%), - radial-gradient(700px 500px at 100% 110%, rgba(234, 179, 8, 0.03), transparent 55%); -} -html[data-layout="bento-hub"] .brand-mark { - background: linear-gradient(145deg, #fbbf24, #ca8a04); - box-shadow: 0 6px 18px -4px rgba(234, 179, 8, 0.45), inset 0 1px 0 rgba(255,255,255,0.25); -} -html[data-layout="bento-hub"] .sidebar { - background: #13181f; - border-right: 1px solid #30363d; -} -html[data-layout="bento-hub"] .brand { - border-bottom: 1px solid #21262d; - padding-bottom: 18px; -} -html[data-layout="bento-hub"] .nav-item { - border-radius: 6px; -} -html[data-layout="bento-hub"] .nav-item.active { - background: rgba(234, 179, 8, 0.08); - color: #fbbf24; -} -html[data-layout="bento-hub"] .nav-item.active::before { - content: ''; - position: absolute; - left: 0; - top: 50%; - transform: translateY(-50%); - width: 3px; - height: 60%; - background: #eab308; - border-radius: 0 3px 3px 0; -} -html[data-layout="bento-hub"] .playlist-item { - background: #0d1117; - border: 1px solid #21262d; - border-radius: 8px; - margin: 3px 0; - padding: 8px 12px; -} -html[data-layout="bento-hub"] .playlist-item.active { - border-color: #eab308; - background: rgba(234, 179, 8, 0.05); -} -html[data-layout="bento-hub"] .topbar { - background: #13181f; - border-bottom: 2px solid #21262d; - backdrop-filter: none; - -webkit-backdrop-filter: none; -} -html[data-layout="bento-hub"] .mini-bar-inner { - background: linear-gradient(180deg, #1c2333, #13181f); - border-top: 1px solid #30363d; -} - /* Grid-column overrides only apply on screens wide enough for an inline sidebar (≥861px). Below that the sidebar is position:fixed (out of grid flow) so a two-column grid would leave .main stranded in the narrow first column. */ diff --git a/frontend/sw.js b/frontend/sw.js index b1756e7..2aada81 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -59,6 +59,9 @@ const SHELL = [ '/service-ui.css', '/refresh.css', '/theme-glass.css', + '/theme-bento-hub.css', + '/fonts/PlusJakartaSans-latin.woff2', + '/fonts/PlusJakartaSans-latin-ext.woff2', '/theme-glass-panel.css', '/glass-panel-layout.js', '/theme-classic.css', @@ -105,6 +108,7 @@ const SHELL = [ '/saved-page.js', '/server-backfill.js', '/glass-controls.js', + '/bento-hub.js', '/theme-glass-controls.css', '/saved-page.css', '/party-dj.js', diff --git a/frontend/theme-bento-hub.css b/frontend/theme-bento-hub.css new file mode 100644 index 0000000..4cb314f --- /dev/null +++ b/frontend/theme-bento-hub.css @@ -0,0 +1,130 @@ +/* Bento Hub — obsidian tiles, solar controls and cyan listening cues. + Reference: docs/mockups/bento-hub. No shared-theme overrides live here. */ +@font-face { font-family:'Plus Jakarta Sans'; font-style:normal; font-weight:400 800; font-display:swap; src:url('fonts/PlusJakartaSans-latin.woff2') format('woff2'); unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; } +@font-face { font-family:'Plus Jakarta Sans'; font-style:normal; font-weight:400 800; font-display:swap; src:url('fonts/PlusJakartaSans-latin-ext.woff2') format('woff2'); unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; } +html[data-layout="bento-hub"] { + --bg:#090b0e; --bg-1:#11151c; --bg-2:#19202d; --bg-3:#222c3e; --bg-glass:rgba(17,21,28,.85); + --line:rgba(255,255,255,.09); --line-soft:rgba(255,255,255,.04); --line-bright:rgba(255,255,255,.18); + --text:#f1f5f9; --text-2:#94a3b8; --text-dim:#8594aa; --on-accent:#090b0e; + --accent:#ffb703; --accent-bright:#ffc527; --accent-deep:#d97706; --accent-glow:rgba(255,183,3,.22); + --accent-sec:#00f5d4; --accent-sec-deep:#00bbf9; --danger:#ef4444; --danger-text:#ff9393; --danger-bg:rgba(239,68,68,.12); --success:#10b981; + --radius:16px; --radius-outer:16px; --radius-inner:10px; --radius-sm:6px; --tile-gap:16px; + --shadow-sm:0 2px 8px rgb(0 0 0/.3); --shadow-md:0 8px 24px rgb(0 0 0/.45); --shadow-lg:0 16px 40px rgb(0 0 0/.6); --shadow:var(--shadow-md); + --ui:'Plus Jakarta Sans',system-ui,sans-serif; --display:var(--ui); --mono:'JetBrains Mono',monospace; color-scheme:dark; +} +html[data-layout="bento-hub"][data-theme="light"] { + --bg:#f4f6f8; --bg-1:#fff; --bg-2:#e9ecef; --bg-3:#dee2e6; --bg-glass:rgba(255,255,255,.88); + --line:rgba(0,0,0,.08); --line-soft:rgba(0,0,0,.04); --line-bright:rgba(0,0,0,.16); + --text:#0f172a; --text-2:#475569; --text-dim:#64748b; --on-accent:#fff; + --accent:#b65f04; --accent-bright:#d97706; --accent-deep:#b45309; --accent-glow:rgba(217,119,6,.18); + --accent-sec:#0d9488; --accent-sec-deep:#0f766e; --danger-text:#b91c1c; + --shadow-sm:0 2px 6px rgb(0 0 0/.05); --shadow-md:0 8px 20px rgb(0 0 0/.08); --shadow-lg:0 16px 32px rgb(0 0 0/.12); color-scheme:light; +} +html[data-layout="bento-hub"][data-theme="contrast"] { --bg:#000; --bg-1:#080808; --bg-2:#161616; --line:#aaa; --line-soft:#666; --text:#fff; --text-2:#ddd; --text-dim:#bbb; } +html[data-layout="bento-hub"] body { background:var(--bg); color:var(--text); font-family:var(--ui); } +html[data-layout="bento-hub"] body::before { display:none; } +html[data-layout="bento-hub"] ::selection { background:var(--accent); color:var(--on-accent); } +html[data-layout="bento-hub"] :is(input,textarea) { caret-color:var(--accent-sec); } +html[data-layout="bento-hub"] * { scrollbar-color:var(--bg-3) var(--bg-1); scrollbar-width:thin; } +html[data-layout="bento-hub"] ::-webkit-scrollbar-thumb { background:var(--bg-3); border-radius:6px; } +html[data-layout="bento-hub"] :is(button,a,input,select,textarea,summary,[tabindex]):focus-visible { outline:2px solid var(--accent-sec); outline-offset:3px; } +html[data-layout="bento-hub"] :is(button,input,select,textarea) { font-family:var(--ui); } +html[data-layout="bento-hub"] :is(button,select,summary) { min-height:44px; } +html[data-layout="bento-hub"] button:disabled { opacity:.45; cursor:not-allowed; } +html[data-layout="bento-hub"] .glass-original-glyph, +html[data-layout="bento-hub"] .legacy-nav-icon { display:none; } +html[data-layout="bento-hub"] :is(.classic-nav-icon,.glass-control-icon) { display:inline-block; width:20px; height:20px; flex:none; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; vertical-align:middle; } +html[data-layout="bento-hub"] .sidebar { background:var(--bg-1); border-right:1px solid var(--line); padding:20px 16px; gap:16px; } +html[data-layout="bento-hub"] .brand { padding:0 0 16px; border-bottom:1px solid var(--line); gap:10px; font-size:18px; font-weight:800; letter-spacing:-.02em; } +html[data-layout="bento-hub"] .brand-mark { width:34px; height:34px; border-radius:10px; background:linear-gradient(135deg,var(--accent),var(--accent-deep)); color:var(--on-accent); box-shadow:var(--shadow-sm); } +html[data-layout="bento-hub"] .nav { gap:6px; } +html[data-layout="bento-hub"] .nav-item { display:flex; align-items:center; gap:12px; min-height:44px; padding:10px 12px; border:1px solid transparent; border-radius:10px; color:var(--text-2); font-size:13px; font-weight:600; } +html[data-layout="bento-hub"] .nav-item:hover { background:var(--bg-2); color:var(--text); } +html[data-layout="bento-hub"] .nav-item.active { background:var(--bg-2); border-color:var(--line-bright); color:var(--accent); box-shadow:inset 3px 0 var(--accent); } +html[data-layout="bento-hub"] .nav-item.active::before { display:none; } +html[data-layout="bento-hub"] :is(.nav-badge,.pl-count) { background:var(--bg-3); color:var(--text-2); font:600 11px var(--mono); } +html[data-layout="bento-hub"] .pl-header { font:700 12px var(--ui); color:var(--text-2); letter-spacing:0; } +html[data-layout="bento-hub"] .playlist-item { display:flex; align-items:center; gap:10px; min-height:44px; padding:10px; border:1px solid var(--line); border-radius:10px; background:var(--bg); margin:6px 0; } +html[data-layout="bento-hub"] .playlist-item.active { background:var(--bg-2); border-color:var(--accent); } +html[data-layout="bento-hub"] .pl-name { min-width:0; font-size:13px; } +html[data-layout="bento-hub"] .sidebar-footer { border-top:1px solid var(--line); color:var(--text-2); font-size:12px; } +html[data-layout="bento-hub"] .topbar { background:var(--bg-1); border-bottom:1px solid var(--line); padding:12px 20px; gap:12px; backdrop-filter:none; } +html[data-layout="bento-hub"] .search-form { max-width:640px; gap:8px; } +html[data-layout="bento-hub"] #searchInput { border:1px solid var(--line); border-radius:999px; background:var(--bg-2); padding:12px 18px; font-size:14px; min-height:44px; box-shadow:none; } +html[data-layout="bento-hub"] .search-btn { border:0; border-radius:999px; min-height:44px; background:var(--accent); color:var(--on-accent); font:700 13px var(--ui); box-shadow:none; } +html[data-layout="bento-hub"] :is(.sidebar-toggle,.icon-btn,.update-icon-btn) { min-width:44px; width:44px; min-height:44px; border-radius:10px; background:var(--bg-2); color:var(--text-2); } +html[data-layout="bento-hub"] .bottom-nav { height:calc(64px + env(safe-area-inset-bottom)); background:var(--bg-glass); border-top:1px solid var(--line); backdrop-filter:blur(16px); -webkit-backdrop-filter:blur(16px); padding:0 4px env(safe-area-inset-bottom); } +html[data-layout="bento-hub"] .bottom-nav-btn { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px; padding:8px 0; min-height:64px; color:var(--text-2); } +html[data-layout="bento-hub"] .bottom-nav-icon { display:grid; place-items:center; width:24px; height:24px; margin:0; transform:none; } +html[data-layout="bento-hub"] .bottom-nav-label { font-size:11px; font-weight:600; letter-spacing:0; } +html[data-layout="bento-hub"] .bottom-nav-btn.active { color:var(--accent); background:transparent; } +html[data-layout="bento-hub"] .bottom-nav-btn.active::before { content:''; position:absolute; top:0; width:24px; height:2px; border-radius:2px; background:var(--accent); } +html[data-layout="bento-hub"] .bottom-nav-badge { left:calc(50% + 6px); top:4px; margin:0; height:18px; min-width:18px; font:700 11px var(--mono); color:var(--on-accent); background:var(--accent); } +html[data-layout="bento-hub"] .player-pane { padding:20px; gap:16px; background:var(--bg); } +html[data-layout="bento-hub"] .list-pane { background:var(--bg); border-left:1px solid var(--line); width:440px; } +html[data-layout="bento-hub"] .player-placeholder { background:var(--bg); border:0; padding:0; } +html[data-layout="bento-hub"] .hero { padding:24px; background:var(--bg-1); border:1px solid var(--line); border-radius:16px; } +html[data-layout="bento-hub"] .hero-title { font:800 32px/1.2 var(--ui); color:var(--text); letter-spacing:-.03em; } +html[data-layout="bento-hub"] .hero-tagline { font-size:14px; color:var(--text-2); max-width:65ch; line-height:1.6; } +html[data-layout="bento-hub"] :is(.hero-pill,.chip) { background:var(--bg-2); color:var(--text-2); border:1px solid var(--line); border-radius:999px; font-size:12px; } +html[data-layout="bento-hub"] .hero-pill::before { background:var(--accent-sec); } +html[data-layout="bento-hub"] .home-pl { padding:0; } +html[data-layout="bento-hub"] .home-pl-title { font:800 22px var(--ui); color:var(--text); } +html[data-layout="bento-hub"] .home-pl-grid { grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); gap:16px; padding:0 0 16px; } +html[data-layout="bento-hub"] .home-pl-card { padding:12px; border:1px solid var(--line); border-radius:16px; background:var(--bg-1); box-shadow:var(--shadow-sm); } +html[data-layout="bento-hub"] .home-pl-thumb { border-radius:10px; aspect-ratio:16/9; } +html[data-layout="bento-hub"] .home-pl-name { padding:10px 0 0; font:700 14px/1.4 var(--ui); } +html[data-layout="bento-hub"] .home-pl-count { padding:4px 0 0; font-size:12px; color:var(--text-2); } +html[data-layout="bento-hub"] .home-pl-play { width:44px; height:44px; color:var(--on-accent); background:var(--accent); border-radius:999px; } +html[data-layout="bento-hub"] :is(.home-pl-new,.list-actions button,.np-btn,.dl-saved-cta,.dl-saved-link) { display:inline-flex; align-items:center; justify-content:center; gap:8px; border:1px solid var(--line); border-radius:10px; padding:10px 14px; background:var(--bg-2); color:var(--text); font:600 13px var(--ui); } +html[data-layout="bento-hub"] :is(.home-pl-new,.list-actions .primary-btn,.primary-btn) { background:var(--accent); color:var(--on-accent); border-color:var(--accent); } +html[data-layout="bento-hub"] .recommendations { border:1px solid var(--line); border-radius:16px; background:var(--bg-1); padding:16px; } +html[data-layout="bento-hub"] .recommendations h2 { font:800 22px var(--ui); } +html[data-layout="bento-hub"] .rec-card { background:var(--bg-2); border:1px solid var(--line); border-radius:10px; } +html[data-layout="bento-hub"] .rec-title { font-size:13px; } +html[data-layout="bento-hub"] .rec-channel { font-size:12px; color:var(--text-2); } +html[data-layout="bento-hub"] .list-header { padding:20px 16px 12px; gap:12px; } +html[data-layout="bento-hub"] .list-header h2 { font:800 22px/1.3 var(--ui); color:var(--text); } +html[data-layout="bento-hub"] .list-filter-wrap { padding:0 16px 12px; } +html[data-layout="bento-hub"] .list-filter { min-height:44px; background:var(--bg-2); border:1px solid var(--line); border-radius:10px; font-size:13px; } +html[data-layout="bento-hub"] .cards { padding:4px 16px 24px; gap:12px; } +html[data-layout="bento-hub"] .cards[data-view="search"], +html[data-layout="bento-hub"] .cards[data-view="channel"], +html[data-layout="bento-hub"] .cards[data-view="library"] { display:grid; grid-template-columns:repeat(auto-fill,minmax(180px,1fr)); align-content:start; gap:16px; } +html[data-layout="bento-hub"] .cards:is([data-view="search"],[data-view="channel"],[data-view="library"]) .card { display:flex; flex-direction:column; align-items:stretch; gap:10px; padding:12px; } +html[data-layout="bento-hub"] .cards:is([data-view="search"],[data-view="channel"],[data-view="library"]) .thumb { width:100%; aspect-ratio:16/9; } +html[data-layout="bento-hub"] .cards:is([data-view="search"],[data-view="channel"],[data-view="library"]) .card-actions { position:static; display:flex; justify-content:flex-end; } +html[data-layout="bento-hub"] .card { animation:none; border:1px solid var(--line); border-radius:16px; background:var(--bg-1); box-shadow:var(--shadow-sm); } +html[data-layout="bento-hub"] .card-title { font:700 13px/1.5 var(--ui); -webkit-line-clamp:2; } +html[data-layout="bento-hub"] .card-channel { font-size:12px; color:var(--text-2); } +html[data-layout="bento-hub"] .thumb { border-radius:10px; } +html[data-layout="bento-hub"] .duration { font:500 11px var(--mono); background:rgba(0,0,0,.8); } +html[data-layout="bento-hub"] .thumb::after { background:none; } +html[data-layout="bento-hub"] .card:hover .thumb img { transform:none; } +html[data-layout="bento-hub"] .card.playing { border-color:var(--accent-sec); background:var(--bg-2); } +html[data-layout="bento-hub"] .card.playing::after { background:var(--accent-sec); } +@media (hover:hover) { + html[data-layout="bento-hub"] :is(.home-pl-card,.cards[data-view="search"] .card):hover { transform:translateY(-2px); border-color:var(--accent); box-shadow:var(--shadow-md); transition:transform .18s ease-out,box-shadow .18s ease-out; } +} +@media (min-width:1024px) { html[data-layout="bento-hub"] .app { grid-template-columns:260px minmax(0,1fr); } } +@media (max-width:860px) { + html[data-layout="bento-hub"] .topbar { padding:12px; gap:8px; } + html[data-layout="bento-hub"] .player-pane { padding:12px; gap:12px; } + html[data-layout="bento-hub"] .list-pane { width:100%; border-left:0; } + html[data-layout="bento-hub"] .home-pl-grid { grid-template-columns:repeat(auto-fill,minmax(160px,1fr)); gap:12px; } + html[data-layout="bento-hub"] .home-pl-title { font-size:18px; } + html[data-layout="bento-hub"] .list-header { padding:16px 12px 12px; } + html[data-layout="bento-hub"] .list-header h2 { font-size:18px; } + html[data-layout="bento-hub"] .cards { padding-inline:12px; } + html[data-layout="bento-hub"] .cards:is([data-view="search"],[data-view="channel"],[data-view="library"]) { grid-template-columns:1fr; gap:12px; } + html[data-layout="bento-hub"] .hero-title { font-size:24px; } +} +@media (prefers-reduced-motion:reduce) { html[data-layout="bento-hub"] *,html[data-layout="bento-hub"] *::before,html[data-layout="bento-hub"] *::after { animation:none!important; transition:none!important; } } + +/* Actual playlist artwork keeps the reference's collage without synthetic data. */ +html[data-layout="bento-hub"] .bento-collage { position:absolute; inset:0; display:grid; grid-template-columns:1fr 1fr; grid-template-rows:1fr 1fr; gap:2px; } +html[data-layout="bento-hub"] .bento-collage img { width:100%; height:100%; object-fit:cover; } +html[data-layout="bento-hub"] .home-pl-thumb:has(.bento-collage) > img { visibility:hidden; } +html[data-layout="bento-hub"] .cards:is([data-view="search"],[data-view="channel"],[data-view="library"]) .card-menu { align-self:flex-end; } +@media(max-width:860px) { html[data-layout="bento-hub"] .bottom-nav { display:flex; } } +html[data-layout="bento-hub"] body.service-open .bottom-nav { display:none; } diff --git a/playwright.bento.config.js b/playwright.bento.config.js new file mode 100644 index 0000000..47edac2 --- /dev/null +++ b/playwright.bento.config.js @@ -0,0 +1,2 @@ +const { defineConfig } = require('@playwright/test'); +module.exports = defineConfig({ testDir:'./tests', testMatch:/bento-hub\.spec\.js/, timeout:30000, workers:2, projects:[{name:'chromium',use:{browserName:'chromium'}},{name:'webkit',use:{browserName:'webkit'}}], use:{baseURL:'http://localhost:8105',serviceWorkers:'block'}, webServer:{command:`npx serve ${process.env.BENTO_BASELINE || 'frontend'} -l 8105 --no-clipboard`,port:8105} }); diff --git a/tests/bento-hub.spec.js b/tests/bento-hub.spec.js new file mode 100644 index 0000000..920f119 --- /dev/null +++ b/tests/bento-hub.spec.js @@ -0,0 +1,52 @@ +const {test,expect}=require('@playwright/test'); +const {openClassic,showPlayingFixture}=require('./helpers/classic-fixture'); +const {mkdirSync,readFileSync}=require('node:fs'); +const {differentPixels}=require('./helpers/png-diff'); +const out='/home/josh/deliverables/ytplayer-done13';mkdirSync(out,{recursive:true}); +const before=process.env.CAPTURE_BEFORE==='1'; +const screens=['home','search','playlist','queue','history','saved','downloads','channel','settings','settings-detail','now-playing','lyrics','chapters','lyric-search','service','modal','empty-queue','empty-saved','empty-downloads']; +async function scene(page,layout,screen){ + await openClassic(page,{populated:true,standalone:page.viewportSize().width<900});await showPlayingFixture(page); + await page.evaluate(({layout,screen})=>{ + data.settings.layout=layout;applyAppearance(); + const videos=data.playlists[0].videos; + API.cacheList=async()=>({ok:true,items:screen==='empty-saved'?[]:videos.map(v=>({...v,size:84000000})),total:screen==='empty-saved'?0:1008000000}); + Downloads.configure({slots:()=>4,files:async()=>[],playlists:()=>data.playlists,jobs:()=>!['downloads','settings-detail'].includes(screen)?[]:['active','queued','paused','failed'].map((status,i)=>({...videos[i],status,error:status==='failed'?'ERROR: [youtube] Video unavailable':null,progress:status==='active'?{received:24000000,total:84000000}:null})),action:()=>{}}); + channelData={name:'Preview fixtures',url:'https://www.youtube.com/@preview',key:'preview',results:videos,loading:false}; + searchResults=screen==='home'?[]:videos; searchAttempted=screen!=='home'; + if(screen==='empty-queue'){queue=[];data.queue=[];} + view=screen==='home'?{type:'search'}:screen==='playlist'?{type:'playlist',id:'studio'}:{type:screen.replace('empty-','')}; + if(screen==='home'){current=null;els.playerPane.classList.add('empty');els.placeholder.classList.remove('hidden');} + if(['now-playing','lyrics','chapters','lyric-search','service','modal'].includes(screen))view={type:'playlist',id:'studio'}; + if(screen==='settings-detail')view={type:'settings'}; + render();renderUpNext(); + if(screen!=='home')showMiniBar(); + if(['lyrics','chapters','lyric-search'].includes(screen)){ + $('notesPanel').classList.remove('hidden'); + const tab=screen==='lyrics'?'lyrics':screen==='chapters'?'markers':'search'; + document.querySelectorAll('.notes-tab').forEach(b=>b.classList.toggle('active',b.dataset.tab===tab)); + ['lyrics','markers','search'].forEach(t=>$(t+'Pane').classList.toggle('hidden',t!==tab)); + $('lyricsList').innerHTML='
Because You are God
You can do anything
You can do anything
Your grace is enough
'; + $('chapterList').innerHTML=''; + } + if(screen==='service'){data.settings.serviceLyrics=true;enterServiceMode(data.playlists[0]);$('serviceLyrics').classList.remove('hidden');$('serviceLyricsList').innerHTML='
Because You are God
You can do anything
';} + if(screen==='modal')showModal('Add to playlist',document.createTextNode('Keep this song in your Sunday setlist.'),[{label:'Cancel',onClick:closeModal},{label:'Add',primary:true,onClick:closeModal}]); + },{layout,screen}); + if(screen==='settings-detail')await page.getByRole('button',{name:/Downloads & storage/}).click(); + await page.addStyleTag({content:'*,*::before,*::after{animation:none!important;transition:none!important;caret-color:transparent!important}'}); + await page.evaluate(async()=>{ await document.fonts.ready; await Promise.all([...document.images].map(img=>{img.loading='eager';return img.decode().catch(()=>{});})); if(document.activeElement instanceof HTMLElement)document.activeElement.blur(); });await page.waitForTimeout(300); +} +for(const width of [390,1440])for(const screen of screens)for(const layout of before?['classic','glass-stage']:['classic','glass-stage','bento-hub'])test(`${layout} ${width} ${screen}`,async({page},info)=>{ + await page.setViewportSize({width,height:844});await scene(page,layout,screen); + const name=`${layout}-${width}-${screen}-${info.project.name}.png`; + const buffer=await page.screenshot({path:`${out}/${before?'before':'after'}-${name}`}); + if(before)return; + if(layout!=='bento-hub'){expect(differentPixels(readFileSync(`${out}/before-${name}`),buffer)).toBeLessThanOrEqual(40);return;} + expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)).toBe(true); + expect(await page.locator('#sectionRail').evaluate(el=>el.parentElement===document.body)).toBe(true); + if(!['service','modal'].includes(screen)&&width===390){const tabs=page.locator('#bottomNav button');for(const b of await tabs.all()){if(await b.isVisible()){const r=await b.boundingBox();expect(r.height).toBeGreaterThanOrEqual(44);}}} +}); +for(const width of [390,1440])test(`reference ${width}`,async({page},info)=>{ + await page.setViewportSize({width,height:844});await page.route('**/__bento-reference',r=>r.fulfill({contentType:'text/html',body:readFileSync('docs/mockups/bento-hub/index.html','utf8')}));await page.goto('/__bento-reference');await page.evaluate(()=>document.fonts.ready); + await page.screenshot({path:`${out}/reference-${width}-${info.project.name}.png`,fullPage:true}); +});