diff --git a/astro.config.mjs b/astro.config.mjs index 5719d5a..6f57d75 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -49,12 +49,12 @@ export default defineConfig({ { tag: 'link', attrs: { rel: 'apple-touch-icon', href: '/apple-touch-icon.png' } }, { tag: 'link', attrs: { rel: 'manifest', href: '/manifest.webmanifest' } }, { tag: 'meta', attrs: { name: 'theme-color', content: '#0d1117' } }, - { tag: 'meta', attrs: { property: 'og:image', content: `${SITE}/img/og-7da9dc1a.jpg` } }, + { tag: 'meta', attrs: { property: 'og:image', content: `${SITE}/img/og-64722d2b.jpg` } }, { tag: 'meta', attrs: { property: 'og:image:width', content: '1200' } }, { tag: 'meta', attrs: { property: 'og:image:height', content: '630' } }, { tag: 'meta', attrs: { property: 'og:site_name', content: 'Stackyard' } }, { tag: 'meta', attrs: { name: 'twitter:card', content: 'summary_large_image' } }, - { tag: 'meta', attrs: { name: 'twitter:image', content: `${SITE}/img/og-7da9dc1a.jpg` } }, + { tag: 'meta', attrs: { name: 'twitter:image', content: `${SITE}/img/og-64722d2b.jpg` } }, ], expressiveCode: { diff --git a/public/img/dashboard-b3d50cc8.jpg b/public/img/dashboard-b3d50cc8.jpg new file mode 100644 index 0000000..c1d2170 Binary files /dev/null and b/public/img/dashboard-b3d50cc8.jpg differ diff --git a/public/img/dashboard-c6746175.jpg b/public/img/dashboard-c6746175.jpg deleted file mode 100644 index fe3fdc3..0000000 Binary files a/public/img/dashboard-c6746175.jpg and /dev/null differ diff --git a/public/img/og-64722d2b.jpg b/public/img/og-64722d2b.jpg new file mode 100644 index 0000000..8f50d38 Binary files /dev/null and b/public/img/og-64722d2b.jpg differ diff --git a/public/img/og-7da9dc1a.jpg b/public/img/og-7da9dc1a.jpg deleted file mode 100644 index ab3eddc..0000000 Binary files a/public/img/og-7da9dc1a.jpg and /dev/null differ diff --git a/public/js/admin-app-form.js b/public/js/admin-app-form.js index ff74f44..a16f367 100644 --- a/public/js/admin-app-form.js +++ b/public/js/admin-app-form.js @@ -1,16 +1,16 @@ -import { clr as rc, el, inp as inpById, q as qSel, qa, qi, tgt } from '/js/utils.js?v=70cbc405'; +import { clr as rc, el, inp as inpById, q as qSel, qa, qi, tgt } from '/js/utils.js?v=5d2b6f16'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { loadLocalIcons, resolveIcon, iconChain, cdnIconRef, splitIconRef } from '/js/icons.js?v=9c8c550c'; import { state } from '/js/admin-state.js?v=5a5d655f'; +import { DOCK_MAX } from '/js/limits.js?v=31048a24'; import { isDockBlocked, - DOCK_MAX, clearsStoredSecret, isBareHostUrl, failureIsMissingApiPath, nextActiveIndex, sameIconName, -} from '/js/admin-logic.js?v=69e57d35'; +} from '/js/admin-logic.js?v=e3673bd7'; import { t } from '/js/i18n.js?v=1f1ea9c1'; import { toast, @@ -21,10 +21,10 @@ import { reveal, setTogDisabled, swapContent, -} from '/js/admin-shared.js?v=fd784739'; -import { createListbox } from '/js/listbox.js?v=bedd1104'; +} from '/js/admin-shared.js?v=69f5c7f9'; +import { createListbox } from '/js/listbox.js?v=2b633751'; import { MAX_LABELS } from '/js/badge-logic.js?v=b3c8b6c2'; -import { renderColorControl, BADGE_SWATCHES, BADGE_DEFAULT } from '/js/admin-color-control.js?v=837961fa'; +import { renderColorControl, BADGE_SWATCHES, BADGE_DEFAULT } from '/js/admin-color-control.js?v=d0c89032'; import { badgeErrorAdvice, TONE } from '/js/admin-error.js?v=10f3cdb1'; import { fluidHoverClear, fluidHoverKb } from '/js/fluid-hover.js?v=cb886e86'; @@ -80,7 +80,7 @@ function _wireFolderApps(apps, children) { } export function buildAppForm(body, item) { - const dockBlocked = isDockBlocked(state.items, item); + const dockBlocked = isDockBlocked(state.items, item, DOCK_MAX); const globalHealthOn = !!inpById('srv-docker-en')?.checked; const mon = item?.monitoring || {}; const hc = mon.healthcheck || { diff --git a/public/js/admin-auth.js b/public/js/admin-auth.js index 5d083e1..6add72f 100644 --- a/public/js/admin-auth.js +++ b/public/js/admin-auth.js @@ -1,7 +1,7 @@ -import { ag, ap } from '/js/admin-shared.js?v=fd784739'; +import { ag, ap } from '/js/admin-shared.js?v=69f5c7f9'; import { t } from '/js/i18n.js?v=1f1ea9c1'; import { pwStrength } from '/js/password-strength.js?v=42f45ac7'; -import { el, inp as inpById, qa } from '/js/utils.js?v=70cbc405'; +import { el, inp as inpById, qa } from '/js/utils.js?v=5d2b6f16'; export async function checkAuth(onLogin) { try { diff --git a/public/js/admin-color-control.js b/public/js/admin-color-control.js index 7486796..26ba049 100644 --- a/public/js/admin-color-control.js +++ b/public/js/admin-color-control.js @@ -1,7 +1,7 @@ -import { PE_SVG, initInlineEdit, toast, reveal } from '/js/admin-shared.js?v=fd784739'; +import { PE_SVG, initInlineEdit, toast, reveal } from '/js/admin-shared.js?v=69f5c7f9'; import { t } from '/js/i18n.js?v=1f1ea9c1'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; -import { qa, q } from '/js/utils.js?v=70cbc405'; +import { qa, q } from '/js/utils.js?v=5d2b6f16'; const CC_SWATCHES = ['#1c1c1e', '#8e8e93', '#f2f2f7', '#ff393c', '#ffcd00', '#35c759', '#0289ff', '#cb30df']; export const BADGE_DEFAULT = '#1e6ef4'; diff --git a/public/js/admin-drag.js b/public/js/admin-drag.js index d439eee..bc9d2d1 100644 --- a/public/js/admin-drag.js +++ b/public/js/admin-drag.js @@ -12,7 +12,7 @@ import { applyDrop, canJoinFolder, folderRowZone } from '/js/admin-drag-logic.js?v=6b767e76'; import { state } from '/js/admin-state.js?v=5a5d655f'; import { snapshotItems } from '/js/admin-save-logic.js?v=4f71ef6c'; -import { qa } from '/js/utils.js?v=70cbc405'; +import { qa } from '/js/utils.js?v=5d2b6f16'; /** @type {(before: unknown) => void} */ let _save = () => {}; diff --git a/public/js/admin-list.js b/public/js/admin-list.js index be890c2..3c08fe1 100644 --- a/public/js/admin-list.js +++ b/public/js/admin-list.js @@ -13,13 +13,13 @@ import { collapsedFolders, filter, state } from '/js/admin-state.js?v=5a5d655f'; import { snapshotItems } from '/js/admin-save-logic.js?v=4f71ef6c'; -import { reorderItems } from '/js/admin-logic.js?v=69e57d35'; -import { initDrag, wireRowDrag } from '/js/admin-drag.js?v=85ce4de5'; -import { paintIcon } from '/js/admin-shared.js?v=fd784739'; -import { clr as rc, el, qa, setUserText } from '/js/utils.js?v=70cbc405'; +import { reorderItems } from '/js/admin-logic.js?v=e3673bd7'; +import { initDrag, wireRowDrag } from '/js/admin-drag.js?v=dbf4e7e6'; +import { paintIcon } from '/js/admin-shared.js?v=69f5c7f9'; +import { clr as rc, el, qa, setUserText } from '/js/utils.js?v=5d2b6f16'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { t } from '/js/i18n.js?v=1f1ea9c1'; -import { sizeLabel } from '/js/admin-widget-form.js?v=3e1f15dd'; +import { sizeLabel } from '/js/admin-widget-form.js?v=c4b11d2a'; import { widgetGlyph } from '/js/widget-glyphs.js?v=12b0a947'; /** @type {{ openModal: (idx: number|null) => void, diff --git a/public/js/admin-logic.js b/public/js/admin-logic.js index a54df69..626d7bf 100644 --- a/public/js/admin-logic.js +++ b/public/js/admin-logic.js @@ -132,8 +132,6 @@ export function nextActiveIndex(key, active, len) { } } -/* dashboard.js slices the dock to DOCK_MAX, so the toggle must refuse beyond - it. */ /* The server never refills a row that arrives non-secret, so unticking always loses the stored value. Say so before the save. */ export function clearsStoredSecret(row, checked) { @@ -203,14 +201,15 @@ export function resolveAdminSection(requested, available) { return list.includes(String(requested ?? '')) ? String(requested) : list[0]; } -export const DOCK_MAX = 4; - -export function isDockBlocked(items, editing) { +/* The limit is a parameter. This module must not import. An import here breaks + every test that loads it without the path hook. + @param {any[]} items @param {any} editing @param {number} max */ +export function isDockBlocked(items, editing, max) { if (editing?.dock) return false; const docked = (Array.isArray(items) ? items : []).filter( i => i?.type === 'app' && i.dock && i.id !== editing?.id, ).length; - return docked >= DOCK_MAX; + return docked >= max; } export function groupBounds(field, size) { diff --git a/public/js/admin-settings.js b/public/js/admin-settings.js index 23bd7c3..708635d 100644 --- a/public/js/admin-settings.js +++ b/public/js/admin-settings.js @@ -1,4 +1,4 @@ -import { toast, ag, ap, reveal, swapContent } from '/js/admin-shared.js?v=fd784739'; +import { toast, ag, ap, reveal, swapContent } from '/js/admin-shared.js?v=69f5c7f9'; import { pwStrength } from '/js/password-strength.js?v=42f45ac7'; import { t } from '/js/i18n.js?v=1f1ea9c1'; import { @@ -7,9 +7,9 @@ import { clearsStoredPassword, createDirtyTracker, BLOCK, -} from '/js/admin-logic.js?v=69e57d35'; +} from '/js/admin-logic.js?v=e3673bd7'; import { confirmText } from '/js/modal.js?v=11fa1eff'; -import { el, inp, setUserText } from '/js/utils.js?v=70cbc405'; +import { el, inp, setUserText } from '/js/utils.js?v=5d2b6f16'; /* Mirrors the server's rule: auth cannot be switched on with no password. */ let _passwordSet = false; diff --git a/public/js/admin-shared.js b/public/js/admin-shared.js index 004193b..571ab88 100644 --- a/public/js/admin-shared.js +++ b/public/js/admin-shared.js @@ -1,6 +1,6 @@ /* Stateless helpers shared by the admin modules. Mutable state stays out. */ -import { recoversSession, toastHoldMs } from '/js/admin-logic.js?v=69e57d35'; -import { el, q } from '/js/utils.js?v=70cbc405'; +import { recoversSession, toastHoldMs } from '/js/admin-logic.js?v=e3673bd7'; +import { el, q } from '/js/utils.js?v=5d2b6f16'; import { t } from '/js/i18n.js?v=1f1ea9c1'; import { iconChain } from '/js/icons.js?v=9c8c550c'; diff --git a/public/js/admin-widget-form.js b/public/js/admin-widget-form.js index a056cc9..1b6d1f8 100644 --- a/public/js/admin-widget-form.js +++ b/public/js/admin-widget-form.js @@ -1,11 +1,11 @@ import { state } from '/js/admin-state.js?v=5a5d655f'; -import { PE_SVG, initInlineEdit, swapContent } from '/js/admin-shared.js?v=fd784739'; -import { createListbox } from '/js/listbox.js?v=bedd1104'; -import { renderWidgetConfigForm } from '/js/widget-config-form.js?v=40e30a39'; +import { PE_SVG, initInlineEdit, swapContent } from '/js/admin-shared.js?v=69f5c7f9'; +import { createListbox } from '/js/listbox.js?v=2b633751'; +import { renderWidgetConfigForm } from '/js/widget-config-form.js?v=253fcdcd'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; -import { sizesForView, widgetConfigMode, rejectionLines, carriesTypedValues } from '/js/admin-logic.js?v=69e57d35'; +import { sizesForView, widgetConfigMode, rejectionLines, carriesTypedValues } from '/js/admin-logic.js?v=e3673bd7'; import { t } from '/js/i18n.js?v=1f1ea9c1'; -import { q, qi, qa } from '/js/utils.js?v=70cbc405'; +import { q, qi, qa } from '/js/utils.js?v=5d2b6f16'; const SIZE_ICONS = { small: diff --git a/public/js/admin.js b/public/js/admin.js index 1de6f87..ac0c351 100644 --- a/public/js/admin.js +++ b/public/js/admin.js @@ -1,7 +1,7 @@ -import { buildAppForm, buildFolderForm, captureActLabels, serializeKvRows } from '/js/admin-app-form.js?v=47949906'; -import { checkAuth, requireLogin, wirePasswordStrength } from '/js/admin-auth.js?v=c70fb722'; -import { initList, render, syncFilterUI } from '/js/admin-list.js?v=94b420e9'; -import { resolveAdminSection } from '/js/admin-logic.js?v=69e57d35'; +import { buildAppForm, buildFolderForm, captureActLabels, serializeKvRows } from '/js/admin-app-form.js?v=7da1ff10'; +import { checkAuth, requireLogin, wirePasswordStrength } from '/js/admin-auth.js?v=e2646986'; +import { initList, render, syncFilterUI } from '/js/admin-list.js?v=c5d8bff8'; +import { resolveAdminSection } from '/js/admin-logic.js?v=e3673bd7'; import { buildAppItem, claimFolderChildren, @@ -10,13 +10,13 @@ import { snapshotItems, upsertItem, } from '/js/admin-save-logic.js?v=4f71ef6c'; -import { loadSettings, settingsDirty, showBgFields, showWallpaperFile } from '/js/admin-settings.js?v=2d1a8a29'; -import { ag, ap, initInlineEdit, paintIcon, reveal, setReauthHandler, toast } from '/js/admin-shared.js?v=fd784739'; +import { loadSettings, settingsDirty, showBgFields, showWallpaperFile } from '/js/admin-settings.js?v=4d5e9919'; +import { ag, ap, initInlineEdit, paintIcon, reveal, setReauthHandler, toast } from '/js/admin-shared.js?v=69f5c7f9'; import { collapsedFolders, filter, state } from '/js/admin-state.js?v=5a5d655f'; -import { buildWidgetForm } from '/js/admin-widget-form.js?v=3e1f15dd'; +import { buildWidgetForm } from '/js/admin-widget-form.js?v=c4b11d2a'; import { initFluidHover } from '/js/fluid-hover.js?v=cb886e86'; import { initGlideSelect, syncGlideSelect } from '/js/glide-select.js?v=8b39e9d0'; -import { createListbox } from '/js/listbox.js?v=bedd1104'; +import { createListbox } from '/js/listbox.js?v=2b633751'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { initI18n, LANGUAGES, t } from '/js/i18n.js?v=1f1ea9c1'; import { loadLocalIcons } from '/js/icons.js?v=9c8c550c'; @@ -32,8 +32,8 @@ import { import { isMobileLayout, onLayoutChange } from '/js/layout.js?v=e9f4b607'; import { confirmModal, confirmText, openModal as openDialog, promptModal } from '/js/modal.js?v=11fa1eff'; import { readMode, watchSystemTheme, writeMode } from '/js/theme.js?v=787bfdff'; -import { el, inp, q, qa, clr as rc, sanitizeCssUrl, setUserText, tgt } from '/js/utils.js?v=70cbc405'; -import { normalizeColorInput } from '/js/admin-color-control.js?v=837961fa'; +import { el, inp, q, qa, clr as rc, sanitizeCssUrl, setUserText, tgt } from '/js/utils.js?v=5d2b6f16'; +import { normalizeColorInput } from '/js/admin-color-control.js?v=d0c89032'; import { parseYamlTolerant, YamlLiteError } from '/js/yaml-lite.js?v=6ebb564c'; import { loadWallpaper, saveWallpaper } from '/js/wallpaper-cache.js?v=c5f8a3e6'; diff --git a/public/js/dashboard.js b/public/js/dashboard.js index 68a4d9f..7cc608e 100644 --- a/public/js/dashboard.js +++ b/public/js/dashboard.js @@ -23,13 +23,14 @@ import { setUserText, teardownWidgets, titleWhenTruncated, -} from '/js/utils.js?v=70cbc405'; +} from '/js/utils.js?v=5d2b6f16'; import { initFluidHover } from '/js/fluid-hover.js?v=cb886e86'; -import { initSpotlight } from '/js/spotlight.js?v=12d9a895'; +import { initSpotlight } from '/js/spotlight.js?v=50bd3af5'; import { html, setHtml, raw } from '/js/html.js?v=c71f8903'; import { initI18n, t, currentLang } from '/js/i18n.js?v=1f1ea9c1'; import { pwStrength, passwordMismatch } from '/js/password-strength.js?v=42f45ac7'; import { sanitizeItemLinks } from '/js/link-url.js?v=54adb40f'; +import { DOCK_MAX } from '/js/limits.js?v=31048a24'; import { initUI, mkFolder, @@ -38,7 +39,7 @@ import { buildMobile, resetMobileChrome, mkFolderGlyph, -} from '/js/ui.js?v=b530cbb6'; +} from '/js/ui.js?v=9317d147'; import { badgeMinimum, badgeSignature, computeBadgeVisual, readBadgeUpdate } from '/js/badge-logic.js?v=b3c8b6c2'; import { formatNumber } from '/js/format-number.js?v=4a5ccef4'; import { closeBadgePopover, wireBadgePopover } from '/js/badge-popover.js?v=aa52b1a3'; @@ -394,7 +395,7 @@ function buildDesktop() { /* Before paginate() and before any tile is built: both size against it. */ gm = gridMetrics(); document.documentElement.style.setProperty('--cols', String(gm.cols)); - const dock = items.filter(i => i.type === 'app' && i.dock && !i.hidden).slice(0, 4); + const dock = items.filter(i => i.type === 'app' && i.dock && !i.hidden).slice(0, DOCK_MAX); document.body.classList.toggle('no-dock', !dock.length); const pages = paginate(); totalPages = pages.length; diff --git a/public/js/limits.js b/public/js/limits.js new file mode 100644 index 0000000..e4a37fd --- /dev/null +++ b/public/js/limits.js @@ -0,0 +1,4 @@ +/* The server requires this file directly. Keep it free of the DOM, of window + and of imports. */ + +export const DOCK_MAX = 4; diff --git a/public/js/listbox.js b/public/js/listbox.js index f325e1d..3dfe163 100644 --- a/public/js/listbox.js +++ b/public/js/listbox.js @@ -6,10 +6,10 @@ Do not use popover. The top layer makes iOS collapse its browser toolbar, which exposes a strip of page canvas and misplaces the menu. */ -import { nextActiveIndex } from '/js/admin-logic.js?v=69e57d35'; +import { nextActiveIndex } from '/js/admin-logic.js?v=e3673bd7'; import { fluidHoverClear, fluidHoverKb } from '/js/fluid-hover.js?v=cb886e86'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; -import { qa } from '/js/utils.js?v=70cbc405'; +import { qa } from '/js/utils.js?v=5d2b6f16'; const CHEV = ''; diff --git a/public/js/spotlight.js b/public/js/spotlight.js index d4c4d65..3a843bb 100644 --- a/public/js/spotlight.js +++ b/public/js/spotlight.js @@ -1,10 +1,12 @@ import { fluidHoverClear, fluidHoverKb } from '/js/fluid-hover.js?v=cb886e86'; -import { mk, clr, el, inp as inpById, q, qa, setUserText } from '/js/utils.js?v=70cbc405'; +import { mk, clr, el, inp as inpById, q, qa, setUserText } from '/js/utils.js?v=5d2b6f16'; import { t } from '/js/i18n.js?v=1f1ea9c1'; /* Attached to the window so a re-open can undo the previous one. */ const _w = /** @type {any} */ (window); +const ACTIVATES_ON_SPACE = 'a[href], button, input, select, textarea, summary, [role="button"], [contenteditable]'; + /* `isMob` is a function, not a flag: the window can cross the breakpoint while this module is loaded. */ export function initSpotlight({ getItems, isMob, CB, iconChain, openFolderDesktop, openFolderMobile, folderGlyph }) { @@ -16,7 +18,8 @@ export function initSpotlight({ getItems, isMob, CB, iconChain, openFolderDeskto const live = el('sres-live'); let si = 0, cur = [], - returnFocus = null; + returnFocus = null, + openGen = 0; inp.setAttribute('role', 'combobox'); inp.setAttribute('aria-autocomplete', 'list'); @@ -174,13 +177,18 @@ export function initSpotlight({ getItems, isMob, CB, iconChain, openFolderDeskto ov.classList.toggle('kb', bottom > 120); } + /* Focus here, not in a later frame: keys typed in between are lost. Keep `on` + before showModal. WebKit refuses focus while the dialog is display:none. */ function open(ch) { if (ov.open) return; + const gen = ++openGen; returnFocus = document.activeElement; - ov.showModal(); ov.classList.add('on'); + ov.showModal(); inp.value = ch || ''; render(inp.value); + inp.focus(); + if (inp.setSelectionRange) inp.setSelectionRange(inp.value.length, inp.value.length); if (MOB() && window.visualViewport) { window.visualViewport.addEventListener('resize', _applyKbLayout); window.visualViewport.addEventListener('scroll', _applyKbLayout); @@ -189,18 +197,11 @@ export function initSpotlight({ getItems, isMob, CB, iconChain, openFolderDeskto window.visualViewport.removeEventListener('scroll', _applyKbLayout); }; } - if (MOB()) { - inp.focus(); - if (inp.setSelectionRange) inp.setSelectionRange(inp.value.length, inp.value.length); - } requestAnimationFrame(() => requestAnimationFrame(() => { + if (gen !== openGen) return; ov.classList.add('vis'); _applyKbLayout(); - if (!MOB()) { - inp.focus(); - if (inp.setSelectionRange) inp.setSelectionRange(inp.value.length, inp.value.length); - } }), ); } @@ -230,6 +231,7 @@ export function initSpotlight({ getItems, isMob, CB, iconChain, openFolderDeskto function close() { if (!ov.open) return; + openGen++; ov.classList.remove('vis'); inp.setAttribute('aria-expanded', 'false'); inp.setAttribute('aria-activedescendant', ''); @@ -291,6 +293,7 @@ export function initSpotlight({ getItems, isMob, CB, iconChain, openFolderDeskto return; } if (e.key === 'Enter') { + if (e.isComposing || e.keyCode === 229) return; const s = qa('.sr', res)[si]; if (s) { s.click(); @@ -304,7 +307,9 @@ export function initSpotlight({ getItems, isMob, CB, iconChain, openFolderDeskto e => { if (ov.classList.contains('on')) return; if (e.key === 'Escape' || e.key.length !== 1 || e.metaKey || e.ctrlKey || e.altKey) return; + if (e.key === ' ' && e.target instanceof Element && e.target.closest(ACTIVATES_ON_SPACE)) return; e.stopImmediatePropagation(); + e.preventDefault(); open(e.key); }, true, diff --git a/public/js/ui.js b/public/js/ui.js index a85705b..e388cb3 100644 --- a/public/js/ui.js +++ b/public/js/ui.js @@ -13,7 +13,7 @@ import { q, qa, setUserText, -} from '/js/utils.js?v=70cbc405'; +} from '/js/utils.js?v=5d2b6f16'; import { t, currentLang } from '/js/i18n.js?v=1f1ea9c1'; import { toneForColor } from '/js/label-contrast.js?v=c1ac6fb8'; import { mobileMetrics, gridColumnWidth, gridCellCount } from '/js/mobile-metrics.js?v=ab5fe77e'; diff --git a/public/js/utils.js b/public/js/utils.js index 88fb097..848f370 100644 --- a/public/js/utils.js +++ b/public/js/utils.js @@ -111,12 +111,9 @@ export const tgt = e => /** @type {HTMLInputElement} */ (e.target); /* breg is passed in to avoid a circular import. */ export function mkWrap(item, sz, r, isz, cls, breg) { const w = mk('div'); - if (cls) w.className = cls; + w.className = cls ? `plate ${cls}` : 'plate'; const wrapBg = item.system === 'settings' ? '#027eae' : clr(item.color); - w.style.cssText = `width:${sz}px;height:${sz}px;border-radius:${r}px;background:${wrapBg};position:relative;flex-shrink:0;overflow:visible;display:flex;align-items:center;justify-content:center;box-shadow:inset 1px 1px 0 rgba(255,255,255,.18),inset -1px -1px 0 rgba(0,0,0,.14);`; - const g = mk('div'); - g.style.cssText = `position:absolute;inset:0;border-radius:${r}px;pointer-events:none;z-index:2;background:linear-gradient(135deg,rgba(255,255,255,.10) 0%,transparent 60%);`; - w.appendChild(g); + w.style.cssText = `--pw:${sz}px;--tc:${wrapBg};width:${sz}px;height:${sz}px;border-radius:${r}px;background-color:${wrapBg};position:relative;flex-shrink:0;overflow:visible;display:flex;align-items:center;justify-content:center;`; const rawIcon = item.iconUrl || ''; if (item.system === 'settings') { const si = Math.min(sz, Math.round(isz * 1.22)); diff --git a/public/js/widget-config-form.js b/public/js/widget-config-form.js index 9b2b7d8..ffe2573 100644 --- a/public/js/widget-config-form.js +++ b/public/js/widget-config-form.js @@ -3,9 +3,9 @@ import { t } from '/js/i18n.js?v=1f1ea9c1'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; -import { reveal, wireInlineEdit } from '/js/admin-shared.js?v=fd784739'; -import { createListbox } from '/js/listbox.js?v=bedd1104'; -import { renderColorControl } from '/js/admin-color-control.js?v=837961fa'; +import { reveal, wireInlineEdit } from '/js/admin-shared.js?v=69f5c7f9'; +import { createListbox } from '/js/listbox.js?v=2b633751'; +import { renderColorControl } from '/js/admin-color-control.js?v=d0c89032'; import { seedCarried, applyOptionSet, @@ -13,9 +13,9 @@ import { requiredFieldMissing, groupBounds, visibleFieldFlags, -} from '/js/admin-logic.js?v=69e57d35'; +} from '/js/admin-logic.js?v=e3673bd7'; import { optionsErrorAdvice, TONE } from '/js/admin-error.js?v=10f3cdb1'; -import { qi } from '/js/utils.js?v=70cbc405'; +import { qi } from '/js/utils.js?v=5d2b6f16'; const PE = ''; diff --git a/src/content/docs/docs/index.md b/src/content/docs/docs/index.md index 4adc261..4d883c7 100644 --- a/src/content/docs/docs/index.md +++ b/src/content/docs/docs/index.md @@ -5,7 +5,7 @@ description: Stackyard is a calm, self-hosted homelab dashboard for your apps, w Stackyard is a self-hosted dashboard for your homelab. Apps, folders, a few widgets and status badges, in one container. -The Stackyard dashboard, with widgets, app tiles, a folder and the dock. +The Stackyard dashboard, with widgets, app tiles, a folder and the dock. - **Calm.** A health badge shows only when something is wrong. - **Badges from any API.** Pick a value from a service's API response. No code. See [Badges](/docs/badges/). diff --git a/src/pages/index.astro b/src/pages/index.astro index d1ed66b..fa5d256 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -10,7 +10,7 @@ const description = const site = Astro.site?.href.replace(/\/$/, '') ?? ''; const canonical = `${site}/`; -const ogImage = `${site}/img/og-7da9dc1a.jpg`; +const ogImage = `${site}/img/og-64722d2b.jpg`; const publisher = { '@type': 'Organization',