chore: articles localization groundwork

This commit is contained in:
Florian Sylvain committed 2026-10-31 21:22:44 +01:00
1 parent 0bdcca8a90
commit 99a74dc3a2
23 files changed
+3524

No files matched your search

+343
View File
@@ -0,0 +1,343 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<dc-import name="LocalizationShell" s="{{ shell }}" mobile="{{ mob }}" slot-body="{{ main }}" slot-overlays="{{ ov }}" src-pane="{{ srcPane }}" ed-banner="{{ edBanner }}" hint-size="100%,100%"></dc-import>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;$preview&quot;:{&quot;width&quot;:1440,&quot;height&quot;:900},&quot;s&quot;:{&quot;editor&quot;:null,&quot;tsType&quot;:&quot;object&quot;},&quot;mobile&quot;:{&quot;editor&quot;:&quot;boolean&quot;,&quot;default&quot;:false,&quot;tsType&quot;:&quot;boolean&quot;}}">
class Component extends DCLogic {
renderVals() {
const s = this.props.s || {}, m = !!this.props.mobile, v = s.v, x = s.x || ''
const P = t => Object.fromEntries(t.split(';').filter(q => q.trim()).map(q => { const i = q.indexOf(':'); let k = q.slice(0, i).trim(); const val = q.slice(i + 1).trim(); if (!k.startsWith('--')) { k = k.replace(/^-webkit-/, 'Webkit-').replace(/-([a-z])/g, (_, c) => c.toUpperCase()) } return [k, val] }))
const h = React.createElement
const D = (t, ...c) => h('div', { style: P(t) }, ...c)
const S = (t, ...c) => h('span', { style: P(t) }, ...c)
const A = (tag, pr, t, ...c) => h(tag, { ...pr, style: P(t) }, ...c)
const Ic = (n, z = 16) => S('display:inline-block;flex-shrink:0;width:' + z + 'px;height:' + z + 'px;background-color:currentColor;-webkit-mask:url(https://api.iconify.design/lucide/' + n + '.svg) center/contain no-repeat;mask:url(https://api.iconify.design/lucide/' + n + '.svg) center/contain no-repeat')
const HL = 'color:var(--ui-text-highlighted)', MU = 'color:var(--ui-text-muted)', TO = 'color:var(--ui-text-toned)'
const mono = "font-family:'IBM Plex Mono',monospace;font-size:12px"
const Btn = (l, k = 'secondary', o = {}) => {
const hh = m ? 44 : (o.sm ? 28 : 32)
const st = { primary: 'background:var(--ui-primary);color:var(--ui-bg)', secondary: 'background:var(--ui-bg);color:var(--ui-text-highlighted);border:1px solid var(--ui-border-accented)', ghost: 'color:var(--ui-text-toned)', danger: 'background:var(--ui-error);color:var(--ui-bg)' }[k]
return A('span', { role: 'button', 'aria-label': o.al || (o.ic && !l ? o.ic : undefined), 'aria-disabled': !!o.dis || undefined, 'aria-busy': o.busy || undefined }, 'display:inline-flex;align-items:center;justify-content:center;gap:6px;height:' + hh + 'px;padding:0 ' + (o.ic && !l ? 0 : 12) + 'px;' + (o.ic && !l ? 'width:' + hh + 'px;' : '') + 'font-weight:500;white-space:nowrap;flex-shrink:0;' + st + ';' + (o.dis ? 'opacity:.4;' : '') + (o.w ? 'width:' + o.w + ';' : ''), o.busy && Ic('loader-circle', 14), o.ic && Ic(o.ic, 16), l)
}
const TN = { neutral: 'background:var(--ui-bg-elevated);color:var(--ui-text-toned)', success: 'background:var(--rc-success-soft);color:var(--ui-success)', info: 'background:var(--rc-info-soft);color:var(--ui-info)', warning: 'background:var(--rc-warning-soft);color:var(--ui-warning)', error: 'background:var(--rc-error-soft);color:var(--ui-error)' }
const Bd = (l, tone = 'neutral', ic) => S('display:inline-flex;align-items:center;gap:4px;height:20px;padding:0 6px;font-size:12px;font-weight:500;white-space:nowrap;flex-shrink:0;' + TN[tone], ic && Ic(ic, 12), l)
// freshness uses an outline so it never reads as a publication state
const Fr = (l, tone = 'warning', ic = 'refresh-cw') => S('display:inline-flex;align-items:center;gap:4px;height:20px;padding:0 6px;font-size:12px;font-weight:500;white-space:nowrap;flex-shrink:0;border:1px solid currentColor;background:var(--ui-bg);color:var(--ui-' + tone + ')', Ic(ic, 12), l)
const Pub = st => st === 'pub' ? Bd('Published', 'success', 'circle-check') : Bd('Draft', 'neutral', 'pencil')
const Alert = (tone, ic, title, text, act) => D('display:flex;gap:10px;padding:12px 14px;align-items:flex-start;' + TN[tone].split(';')[0] + ';color:var(--ui-text-highlighted)', S('color:var(--ui-' + (tone === 'neutral' ? 'text-toned' : tone) + ')', Ic(ic)), D('flex:1;min-width:0;display:flex;flex-direction:column;gap:2px', S('font-weight:600', title), text && S(TO + ';text-wrap:pretty', text), act && D('margin-top:8px;display:flex;gap:8px;flex-wrap:wrap', ...[].concat(act))))
const Fd = (label, val, o = {}) => D('display:flex;flex-direction:column;gap:6px;min-width:0', S('font-weight:500;' + HL, label, o.opt && S(MU + ';font-weight:400', ' (optional)')),
D('display:flex;align-items:' + (o.area ? 'flex-start' : 'center') + ';gap:8px;min-height:' + (m ? 44 : 32) + 'px;padding:' + (o.area ? 8 : 0) + 'px 10px;border:1px solid ' + (o.err ? 'var(--ui-error)' : o.focus ? 'var(--ui-primary)' : 'var(--ui-border-accented)') + ';background:' + (o.ro ? 'var(--ui-bg-elevated)' : 'var(--ui-bg)') + ';' + (o.focus ? 'outline:2px solid var(--ui-primary);outline-offset:1px;' : ''), o.pre && S(MU + ';' + mono + ';white-space:nowrap', o.pre), A('span', { dir: o.dir, lang: o.lang }, 'flex:1;min-width:0;' + (o.mono ? mono + ';' : '') + (o.val === false ? MU : HL) + ';overflow:hidden;text-overflow:ellipsis;' + (o.area ? 'white-space:normal' : 'white-space:nowrap'), val), o.sel && Ic('chevrons-up-down', 14), o.lock && Ic('lock', 14)),
o.err ? S('display:flex;gap:6px;color:var(--ui-error);font-size:12px;line-height:18px', Ic('circle-x', 14), o.err) : o.help && S(MU + ';font-size:12px;line-height:18px;text-wrap:pretty', o.help))
const Radio = (l, d, on) => D('display:flex;gap:10px;padding:10px 12px;border:1px solid ' + (on ? 'var(--ui-primary)' : 'var(--ui-border-accented)') + ';background:' + (on ? 'var(--ui-bg-elevated)' : 'var(--ui-bg)'), S('width:16px;height:16px;margin-top:2px;flex-shrink:0;border:1px solid var(--ui-border-accented);display:flex;align-items:center;justify-content:center', on && S('width:8px;height:8px;background:var(--ui-primary)')), D('display:flex;flex-direction:column;gap:2px', S('font-weight:500;' + HL, l), d && S(MU + ';font-size:12px;line-height:18px;text-wrap:pretty', d)))
const Dlg = (o, body, foot) => D('position:absolute;inset:0;z-index:' + (o.z || 50) + ';background:var(--rc-overlay);display:flex;align-items:' + (m ? 'flex-end' : 'flex-start') + ';justify-content:center;padding-top:' + (m ? 0 : o.top ?? 100) + 'px',
A('div', { role: 'dialog', 'aria-modal': 'true', 'aria-label': o.title }, 'width:' + (m ? '100%' : (o.w || 460) + 'px') + ';max-height:' + (m ? '92%' : 'calc(100% - ' + ((o.top ?? 100) + 24) + 'px)') + ';background:var(--ui-bg);box-shadow:var(--rc-shadow-modal);display:flex;flex-direction:column;' + (m ? 'border-top:1px solid var(--ui-border)' : ''),
m && D('display:flex;justify-content:center;padding:8px 0 0', S('width:36px;height:4px;background:var(--ui-border-accented)')),
D('padding:20px 24px 0;display:flex;gap:12px;align-items:flex-start', D('flex:1;display:flex;flex-direction:column;gap:4px;min-width:0', S('font:600 16px/24px \'IBM Plex Sans\',sans-serif;' + HL, o.title), o.desc && S(TO + ';text-wrap:pretty', o.desc)), Btn('', 'ghost', { ic: 'x', sm: true, al: 'Close' })),
D('padding:16px 24px 0;display:flex;flex-direction:column;gap:14px;overflow-y:auto;min-height:0', ...[].concat(body)),
D('padding:20px 24px;flex-shrink:0;flex-wrap:wrap;display:flex;gap:8px;' + (m ? 'flex-direction:column-reverse' : 'justify-content:flex-end'), ...[].concat(foot))))
const Toast = (tone, ic, t, d) => D('position:absolute;z-index:80;' + (m ? 'left:16px;right:16px;bottom:16px' : 'right:24px;bottom:24px;width:356px') + ';background:var(--ui-bg);border:1px solid var(--ui-border);box-shadow:var(--rc-shadow-pop);padding:12px;display:flex;gap:10px', S('width:28px;height:28px;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:var(--ui-' + tone + ');' + TN[tone].split(';')[0], Ic(ic)), D('display:flex;flex-direction:column;gap:2px', S('font-weight:600;' + HL, t), d && S(TO, d)))
const Sk = n => D('display:flex;flex-direction:column;gap:10px', ...Array.from({ length: n }, (_, i) => D('height:' + (m ? 56 : 44) + 'px;background:var(--ui-bg-elevated);opacity:' + (1 - i * .12))))
const Page = (title, sub, act, ...c) => D('flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column;gap:16px;padding:' + (m ? '16px 16px 88px' : '24px') + ';position:relative', D('display:flex;gap:12px;align-items:flex-start;' + (m ? 'flex-direction:column' : ''), D('flex:1;min-width:0;display:flex;flex-direction:column;gap:2px', S('font:600 20px/28px \'IBM Plex Sans\',sans-serif;' + HL, title), sub && S(TO + ';text-wrap:pretty', sub)), act), ...c)
const Card = (...c) => D('background:var(--ui-bg);border:1px solid var(--ui-border);padding:16px;display:flex;flex-direction:column;gap:12px', ...c)
const ROW = 'display:flex;align-items:center;gap:12px;border-top:1px solid var(--ui-border);padding:10px 16px;min-height:' + (m ? 56 : 48) + 'px'
const Av = i => S('width:26px;height:26px;flex-shrink:0;background:var(--rc-sand);color:var(--rc-on-sand);display:flex;align-items:center;justify-content:center;font:600 11px \'IBM Plex Sans\',sans-serif', i)
let main = null, shell = { kind: 'slot', display: 'Marta Alves', role: s.role }, ov = []
// ---- data
const FX = window.LOCFX, S1 = FX.STORIES[0], S5 = FX.STORIES[4]
const LV = (k, n, c, st) => ({ n, c, lang: k, dir: FX.DIR[k] || 'ltr', t: k === 'en' ? (st || S1).title : (st || S1).tr[k], p: FX.P[k] })
const EN = LV('en', 'English', 'EN'), FR = LV('fr', 'French', 'FR'), AR = LV('ar', 'Arabic', 'AR'), DE = LV('de', 'German', 'DE')
const CAPS = { full: { e: 1, p: 1, d: 1, c: 1, rv: 1 }, ro: {}, p2: { e: 1, d: 1, c: 1, rv: 1 }, p3: { p: 1 }, p6: { e: 1, p: 1, d: 1, c: 1, rv: 1, part: 1 } }
// ---- language menu (menu button + menu; rows are single menu items, no nested controls)
const LangBtn = (cur, o = {}) => A('span', { role: 'button', 'aria-haspopup': 'menu', 'aria-expanded': !!o.open, 'aria-label': 'Language: ' + cur + '. Switch version' }, 'display:inline-flex;align-items:center;gap:8px;height:' + (m ? 44 : 32) + 'px;padding:0 10px;border:1px solid ' + (o.open ? 'var(--ui-primary)' : 'var(--ui-border-accented)') + ';background:var(--ui-bg);color:var(--ui-text-highlighted);font-weight:500;flex-shrink:0;max-width:100%;width:max-content', Ic('languages'), S('min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap', cur), o.code && S(mono + ';flex-shrink:0;' + MU, o.code), Ic('chevrons-up-down', 14))
const Tag = (l, ic) => S('display:inline-flex;align-items:center;gap:4px;font-size:12px;' + MU, ic && Ic(ic, 12), l)
const LRow = r => { const cur = r.cur, add = r.add
return A('div', { role: 'menuitem', 'aria-current': cur || undefined, tabIndex: -1 }, 'display:flex;align-items:center;gap:10px;min-height:' + (m ? 52 : 40) + 'px;padding:6px 12px;' + (cur ? 'background:var(--rc-sand);color:var(--rc-on-sand)' : add ? 'color:var(--ui-text-toned)' : ''), S('width:16px', cur && Ic('check')), D('flex:1;min-width:0;display:flex;flex-direction:column', S('overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:' + (cur ? 600 : 500), r.n), S(mono + (cur ? '' : ';' + MU), r.c)),
add ? S('display:inline-flex;align-items:center;gap:4px;font-weight:500', Ic('plus', 14), 'Add translation') : D('display:flex;gap:4px;flex-wrap:wrap;justify-content:flex-end', r.tag && Tag(r.tag, r.tagIc), Pub(r.st), r.fr && Fr('Needs review'), r.dis && Bd('Disabled', 'neutral', 'eye-off'))) }
const LangPop = (rows, o = {}) => D('position:absolute;z-index:40;' + (m ? 'left:0;right:0;bottom:0;max-height:70%;border-top:1px solid var(--ui-border)' : 'left:' + (o.left ?? 292) + 'px;top:' + (o.top ?? 150) + 'px;width:400px;max-height:' + (o.max ?? 420) + 'px;border:1px solid var(--ui-border)') + ';background:var(--ui-bg);box-shadow:var(--rc-shadow-pop);display:flex;flex-direction:column',
m && D('display:flex;justify-content:center;padding:8px 0 0;flex-shrink:0', S('width:36px;height:4px;background:var(--ui-border-accented)')),
D('padding:10px 12px 6px;display:flex;flex-direction:column;gap:6px;flex-shrink:0', S('font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;' + MU, 'Versions you can open'), o.find && Fd('', 'Find a language', { val: false })),
A('div', { role: 'menu', 'aria-label': 'Versions of this story' }, 'display:flex;flex-direction:column;overflow-y:auto;min-height:0', ...rows.map(LRow)),
D('padding:10px 12px;border-top:1px solid var(--ui-border);display:flex;gap:8px;flex-shrink:0;' + MU + ';font-size:12px;line-height:18px', Ic('info', 14), 'Switching opens that version. The interface language stays the same.'))
const MENUS = {
all: [{ n: 'English', c: 'EN', st: 'pub' }, { n: 'French', c: 'FR', st: 'draft', fr: 1, cur: 1 }, { n: 'German', c: 'DE', st: 'pub' }, { n: 'Arabic', c: 'AR', st: 'draft', fr: 1 }],
two: [{ n: 'English', c: 'EN', st: 'pub' }, { n: 'French', c: 'FR', st: 'draft', fr: 1, cur: 1 }],
many: [{ n: 'English', c: 'EN', st: 'pub' }, { n: 'English (United Kingdom)', c: 'EN-GB', st: 'draft' }, { n: 'French', c: 'FR', st: 'draft', fr: 1, cur: 1 }, { n: 'German', c: 'DE', st: 'pub' }, { n: 'Italian', c: 'IT', st: 'draft' }, { n: 'Japanese', c: 'JA', st: 'pub' }, { n: 'Portuguese (Brazil)', c: 'PT-BR', add: 1 }, { n: 'Spanish', c: 'ES', add: 1 }, { n: 'Chinese (Simplified)', c: 'ZH-HANS', add: 1 }, { n: 'Arabic', c: 'AR', add: 1 }, { n: 'Dutch', c: 'NL', add: 1 }, { n: 'Polish', c: 'PL', add: 1 }],
ro: [{ n: 'English', c: 'EN', st: 'pub' }, { n: 'French', c: 'FR', st: 'draft', fr: 1, cur: 1 }, { n: 'German', c: 'DE', st: 'pub' }],
p4: [{ n: 'English', c: 'EN', st: 'pub' }, { n: 'French', c: 'FR', st: 'draft', fr: 1, cur: 1 }, { n: 'German', c: 'DE', st: 'pub' }, { n: 'Arabic', c: 'AR', st: 'draft', fr: 1, tag: 'Read only', tagIc: 'lock' }],
p5: [{ n: 'English', c: 'EN', st: 'pub' }, { n: 'French', c: 'FR', st: 'draft', fr: 1, cur: 1 }, { n: 'German', c: 'DE', st: 'pub' }]
}
// ---- editor pieces
const ToolRow = ro => D('display:flex;gap:2px;padding:6px;border:1px solid var(--ui-border);background:var(--ui-bg);overflow:hidden;flex-shrink:0;' + TO, A('div', { role: 'toolbar', 'aria-label': 'Formatting' }, 'display:flex;gap:2px', ...['bold', 'italic', 'underline', 'heading-2', 'list', 'list-ordered', 'quote', 'link', 'image'].map(n => A('span', { role: 'button', 'aria-label': n }, 'width:' + (m ? 44 : 28) + 'px;height:' + (m ? 44 : 28) + 'px;display:flex;align-items:center;justify-content:center;flex-shrink:0', Ic(n)))))
const Body = (L, o = {}) => A('div', { dir: L.dir, lang: L.lang, role: 'textbox', 'aria-multiline': 'true', 'aria-readonly': o.ro || undefined, 'aria-label': (o.label || 'Body') + ' (' + L.n + ')', tabIndex: 0 }, 'position:relative;display:flex;flex-direction:column;gap:12px;padding:16px;border:1px solid var(--ui-border);background:' + (o.ro ? 'var(--ui-bg-muted)' : 'var(--ui-bg)') + ';' + ((m && !o.pane) ? 'flex:none;' : 'flex:1;min-height:0;overflow:auto;') + 'font:400 ' + (L.dir === 'rtl' ? '17px/30px' : '15px/26px') + ' \'Source Serif 4\',serif;color:var(--ui-text)', ...(o.long ? L.p.concat(L.p, L.p) : L.p).map(t => S('text-wrap:pretty', t)))
const TitleField = (L, o) => {
const cap = o.cap, v = o.titleVal ?? L.t
if (!cap.e) return D('display:flex;flex-direction:column;gap:2px', S('font-size:12px;' + MU, 'Title (' + L.n + ')'), A('div', { dir: L.dir, lang: L.lang, role: 'textbox', 'aria-readonly': 'true', 'aria-label': 'Title (' + L.n + ')', tabIndex: 0 }, 'font:600 18px/26px \'IBM Plex Sans\',sans-serif;' + HL, v))
const st = o.titleState, err = st === 'invalid' || st === 'failed'
return D('display:flex;flex-direction:column;gap:4px', S('font-size:12px;' + MU, 'Title (' + L.n + ')'),
D('display:flex;align-items:center;gap:8px;min-height:' + (m ? 44 : 36) + 'px;padding:2px 10px;border:1px solid ' + (err ? 'var(--ui-error)' : st === 'focus' || st === 'dirty' ? 'var(--ui-primary)' : 'var(--ui-border-accented)') + ';background:var(--ui-bg);' + (st === 'focus' ? 'outline:2px solid var(--ui-primary);outline-offset:1px' : ''),
A('div', { dir: L.dir, lang: L.lang, role: 'textbox', 'aria-label': 'Title (' + L.n + ')', 'aria-invalid': err || undefined, tabIndex: 0 }, 'flex:1;min-width:0;font:600 18px/26px \'IBM Plex Sans\',sans-serif;' + (st === 'invalid' ? MU : HL) + ';text-wrap:pretty', st === 'invalid' ? 'Title' : v), st === 'saving' ? Ic('loader-circle', 14) : Ic('pencil', 14)),
st === 'invalid' ? S('display:flex;gap:6px;color:var(--ui-error);font-size:12px', Ic('circle-x', 14), 'Enter a title.') : st === 'failed' ? D('display:flex;gap:8px;align-items:center;flex-wrap:wrap;color:var(--ui-error);font-size:12px', Ic('circle-x', 14), 'Couldn\'t save the title. Your text is kept.', Btn('Try again', 'secondary', { sm: true })) : st === 'saving' ? S(MU + ';font-size:12px', 'Saving title…') : st === 'dirty' || st === 'focus' ? S(MU + ';font-size:12px', 'Changing the title doesn\'t change the address. Edit the address in Search and sharing.') : null)
}
const PubAct = (L, o) => ({ l: o.pub === 'pub' ? 'Unpublish ' + L.n : (o.dirty ? 'Save and publish ' : 'Publish ') + L.n, dis: !!o.saving || !!o.saveBlock })
const Life = (L, o) => (o.pub === 'pub' ? 'This version is online. Saving updates the live page right away.' : 'Saving keeps this version as a draft. It isn\'t public until you publish it.') + (o.cap.p && o.pub !== 'pub' ? ' Save and publish saves first, then publishes.' : '')
const SaveLbl = o => o.saving ? 'Saving…' : o.saveFail ? 'Try saving again' : 'Save'
const SaveDis = o => (!o.dirty && !o.saving && !o.saveFail) || !!o.saveBlock
const Edit = (L, o) => {
const cap = o.cap, sr = !!o.src, pa = PubAct(L, o)
const state = o.saveFail ? Bd('Save failed', 'error', 'circle-x') : o.dirty ? Bd('Unsaved changes', 'neutral', 'circle-dot') : null
const btns = []
btns.push(Btn(sr ? 'Hide source reference' : 'Show source reference', 'secondary', { ic: 'columns-2' }))
if (cap.e) btns.push(Btn(SaveLbl(o), 'secondary', { busy: o.saving, dis: SaveDis(o) }))
if (cap.p) btns.push(Btn(pa.l, 'primary', { dis: pa.dis || o.pubDis }))
if (cap.d || cap.c) btns.push(Btn('', 'ghost', { ic: 'ellipsis', al: 'More actions' }))
return D('display:flex;flex-direction:column;gap:10px;flex-shrink:0', D('display:flex;align-items:center;gap:8px;' + MU, Ic('arrow-left', 14), S('font-size:12px', 'Articles')),
TitleField(L, o),
D('display:flex;align-items:center;gap:8px;flex-wrap:wrap', LangBtn(L.n, { code: L.c, open: o.open }), Pub(o.pub || 'draft'), o.fresh && Fr(o.fresh), state, !cap.e && Bd('Read only', 'neutral', 'lock'), ...(m ? [] : [D('flex:1'), ...btns])),
m && D('display:flex;gap:8px', ...btns.slice(0, 1)),
cap.e && o.saveFail && !o.titleState && Alert('error', 'circle-x', 'Couldn\'t save ' + L.n, 'Your changes are kept here. Nothing was published. Try saving again.'),
cap.e && o.dirty && !o.saveFail && !o.titleState && S(MU + ';font-size:12px;line-height:18px;text-wrap:pretty', Life(L, o)),
!cap.e && Alert('neutral', 'lock', cap.p ? 'You can publish this version but not edit it' : 'You can read this version', cap.p ? 'Fields are read-only. Publish and Unpublish stay available. The source reference is still available.' : 'You can\'t edit it. The source reference and versions you can read stay available.'),
cap.e && !cap.p && Alert('neutral', 'info', 'You can edit this version but not publish it', (o.pub === 'pub' ? 'This version is online, so saving updates the live page right away.' : 'Saving keeps it as a draft.') + ' Publishing and unpublishing need the publish permission.'))
}
const MBar = (L, o) => { if (!m) return null; const cap = o.cap, pa = PubAct(L, o), bs = []; if (cap.e) bs.push(Btn(SaveLbl(o), 'secondary', { busy: o.saving, dis: SaveDis(o) })); if (cap.p) bs.push(D('flex:1 1 170px;display:flex', Btn(pa.l, 'primary', { w: '100%', dis: pa.dis }))); if (cap.d || cap.c) bs.push(Btn('', 'ghost', { ic: 'ellipsis', al: 'More actions' })); return bs.length ? D('position:absolute;left:0;right:0;bottom:0;z-index:30;background:var(--ui-bg);border-top:1px solid var(--ui-border);padding:12px 16px;display:flex;gap:8px;flex-wrap:wrap', ...bs) : null }
const Seg = (on, L, S0) => A('div', { role: 'radiogroup', 'aria-label': 'Show' }, 'display:flex;border:1px solid var(--ui-border-accented);flex-shrink:0', ...['Source (' + S0.n + ')', 'Edit (' + L.n + ')'].map((t, i) => A('span', { role: 'radio', 'aria-checked': (on ? i === 0 : i === 1) }, 'flex:1;height:44px;display:flex;align-items:center;justify-content:center;font-weight:500;background:' + ((on ? i === 0 : i === 1) ? 'var(--ui-primary)' : 'var(--ui-bg)') + ';color:' + ((on ? i === 0 : i === 1) ? 'var(--ui-bg)' : 'var(--ui-text-toned)'), t)))
const Src = (S0, o = {}) => D('flex:1;min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px',
D('display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--ui-bg-elevated);flex-wrap:wrap', Ic('book-open', 14), S('font-weight:600;' + HL, 'Reference: ' + S0.n), S(mono + ';' + MU, S0.c), Pub('pub'), S('margin-left:auto;display:inline-flex;gap:4px;' + MU + ';font-size:12px', Ic('lock', 14), 'Read only'), Btn('Change reference', 'secondary', { sm: true })),
S(MU + ';font-size:12px;line-height:18px', 'Reference only. The tracked source for review is English, revision 8. Changing the reference doesn\'t change it.'),
o.gone ? Alert('warning', 'triangle-alert', 'This reference isn\'t available', 'It was deleted or you can no longer read it. Your French version isn\'t affected.', Btn('Choose another reference', 'secondary', { sm: true })) : [
o.chg && Alert('neutral', 'info', 'Changed fields since French was reviewed (revision 7 → 8)', 'Title, Body (2 paragraphs). Fields are listed, not compared line by line.'),
A('div', { dir: S0.dir, lang: S0.lang, role: 'heading', 'aria-level': '2', tabIndex: 0 }, 'font:600 16px/24px \'IBM Plex Sans\',sans-serif;' + HL, S0.t),
Body(S0, { ro: true, label: 'Reference', long: o.long, pane: true })])
const EdPage = (L, o, extra) => D('flex:1;min-width:0;min-height:0;overflow:' + (m ? 'auto' : 'hidden') + ';display:flex;flex-direction:column;gap:12px;padding:' + (m ? '16px 16px 128px' : '24px') + ';position:relative', Edit(L, o), ...[].concat(extra || []), (o.cap.e && !(m && o.srcMob)) ? ToolRow() : null, (m && o.srcMob) ? null : Body(L, { ro: !o.cap.e, long: o.long }), (!m && o.seoNote) ? Alert('neutral', 'info', 'Search and sharing: description is empty', 'No fallback: the English site default isn\'t used for French. Add a French description or the tag is left out.') : null)
const Editor = (L, o, extra) => {
const S0 = o.S0 || EN
if (!o.src) return D('flex:1;min-height:0;display:flex;position:relative', EdPage(L, o, extra), MBar(L, o))
if (m) { const on = !!o.srcMob; return D('flex:1;min-height:0;position:relative;display:flex;flex-direction:column', D('flex:1;min-height:0;display:flex;flex-direction:column;gap:12px;padding:16px 16px 128px', Seg(on, L, S0), on ? Src(S0, o) : D('flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:12px', Edit(L, { ...o, src: 1 }), o.cap.e && ToolRow(), Body(L, { ro: !o.cap.e, long: o.long }))), MBar(L, o)) }
return D('flex:1;min-height:0;display:flex', D('width:' + (o.narrow ? '42%' : '50%') + ';flex-shrink:0;min-width:0;border-right:1px solid var(--ui-border);overflow:hidden;padding:24px;display:flex;flex-direction:column;background:var(--ui-bg-muted)', Src(S0, o)), EdPage(L, o, extra))
}
// ---- site shells
const setS = { nav: 'settings', crumbs: ['Settings', 'Sites', 'Journal', 'Languages'], mTitle: 'Journal · Languages', mLead: 'arrow-left' }
const edS = { nav: 'articles', crumbs: ['Articles', 'Editor'], mTitle: 'Editor', mLead: 'arrow-left' }
const liS = { nav: 'articles', crumbs: ['Articles'], mTitle: 'Articles' }
// ---- site languages
const LNG = FX.LANGS.map(l => { const c = FX.count(l.code, FX.STORIES); return [l.name, l.code, l.def ? 'Default' : l.enabled ? '' : 'Disabled', c[0], c[1], l.social] })
const LRows = ro => LNG.map(r => D(ROW + ';opacity:' + (r[2] === 'Disabled' ? .8 : 1), D('flex:1;min-width:0;display:flex;flex-direction:column', S('font-weight:500;' + HL + ';overflow:hidden;text-overflow:ellipsis;white-space:nowrap', r[0]), S(mono + ';' + MU, r[1] + ' · social locale ' + (r[5] === 'Not set' ? 'not set' : r[5]))), r[2] === 'Default' ? Bd('Default', 'info', 'star') : r[2] === 'Disabled' ? Bd('Disabled', 'neutral', 'eye-off') : null, !m && S(MU + ';width:190px', r[3] + (r[3] === 1 ? ' version · ' : ' versions · ') + r[4] + ' published'), !ro && r[2] === 'Disabled' ? Btn('Re-enable', 'secondary', { sm: true }) : null, !ro && Btn('', 'ghost', { ic: 'ellipsis', sm: true, al: 'Actions for ' + r[0] })))
const slPage = () => {
const ro = x === 'readonly', single = x === 'single', site = single ? 'Archive' : 'Journal', T = 'Languages', SUB = 'Content languages for ' + site + '. Each article version is written in one language.'
if (x === 'loading') return Page(T, SUB, null, Sk(4))
if (x === 'error') return Page(T, SUB, null, Alert('error', 'circle-x', 'Couldn\'t load languages', 'Check your connection and try again.', Btn('Try again', 'secondary', { sm: true })))
return Page(T, SUB, !ro && Btn('Add language', 'primary', { ic: 'plus' }),
ro && Alert('neutral', 'lock', 'Only owners can change languages', 'You can see which languages are enabled and how many versions each has. Ask an owner to add, disable or change a language.'),
x === 'disableDefault' && Alert('warning', 'triangle-alert', 'English is the default', 'Choose another default first, then disable English.'),
x === 'disableLast' && Alert('warning', 'triangle-alert', 'At least one language must stay enabled', 'Journal needs a language for new articles.'),
single ? Card(S('font-weight:600;' + HL, 'Archive uses one language'), S(TO, 'New articles are written in Portuguese (Brazil). Add a language to write translations and publish them separately.'), D('display:flex;gap:10px;align-items:center', Bd('Default', 'info', 'star'), S('font-weight:500;' + HL, 'Portuguese (Brazil)'), S(mono + ';' + MU, 'pt-BR'))) :
D('background:var(--ui-bg);border:1px solid var(--ui-border)', D('padding:8px 16px;background:var(--ui-bg-elevated);' + MU + ';font-weight:500', 'Language'), ...LRows(ro)),
single && Alert('info', 'info', 'Regional language kept: pt-BR', 'Setup kept the full site locale, so the default content language is Portuguese (Brazil), not plain Portuguese. The site locale still formats dates and numbers. Social metadata locale: pt_BR, taken from the language tag.'),
!single && !ro && S(MU + ';font-size:12px;text-wrap:pretty', 'Disabling a language keeps its versions readable and editable, and published ones stay online. Drafts can be published again after you re-enable it.'))
}
const slOv = () => {
const addBody = (err) => [err && Alert('error', 'circle-x', 'Couldn\'t save languages', 'Nothing changed. Your selection is kept. Try again.'), Fd('Language', 'Portuguese (Brazil)', { sel: true, focus: !err }), Fd('Language code', 'pt-BR', { mono: true, ro: true, lock: true, help: 'Used in the API and in page language metadata.' }), Fd('Social metadata locale', 'pt_BR', { mono: true, ro: true, lock: true, help: 'Taken from the language code. For a code without a region, such as fr, an owner sets one or leaves it empty and og:locale is left out.' })]
if (x === 'add') return Dlg({ title: 'Add a language', desc: 'Choose a language for Journal. Regional variants such as English (United Kingdom) are separate entries.' }, addBody(), [Btn('Cancel'), Btn('Add language', 'primary')])
if (x === 'saveFail') return Dlg({ title: 'Add a language', desc: 'Choose a language for Journal.' }, addBody(true), [Btn('Cancel'), Btn('Try again', 'primary')])
if (x === 'adddup') return Dlg({ title: 'Add a language', desc: 'Choose a language for Journal.' }, [Fd('Language', 'French', { sel: true, err: 'French is already enabled for Journal.' })], [Btn('Cancel'), Btn('Add language', 'primary', { dis: true })])
if (x === 'default') return Dlg({ title: 'Make French the default language?', desc: 'New articles start in the default language, and it is the first choice for each story\'s title in the list.' }, [Alert('info', 'info', 'No content changes', 'Existing versions keep their language, status and links. Public requests that name no language will resolve French where a published French version exists, and return 404 where one doesn\'t. Requests that name a language aren\'t affected.')], [Btn('Cancel'), Btn('Make French default', 'primary')])
if (x === 'disable') return Dlg({ title: 'Disable German?', desc: 'German is used by 4 versions: 1 draft and 3 published.' }, [Alert('warning', 'triangle-alert', 'Nothing is deleted or unpublished', 'The 3 published German versions stay online. All 4 versions stay readable and editable. The draft can\'t be published until you re-enable German. Nobody can create new German versions.')], [Btn('Cancel'), Btn('Disable German', 'primary')])
if (x === 'reenable') return Dlg({ title: 'Re-enable Spanish?', desc: 'Writers can create Spanish versions again. The 1 existing Spanish version stays as it is. Nothing else changes.' }, [], [Btn('Cancel'), Btn('Re-enable Spanish', 'primary')])
if (x === 'remove') return Dlg({ title: 'Remove Italian?', desc: 'No article uses Italian. You can add it again later.' }, [], [Btn('Cancel'), Btn('Remove Italian', 'danger')])
if (x === 'removeBlocked') return Dlg({ title: 'Spanish can\'t be removed', desc: 'Spanish is used by 1 version. Disable the language to keep it, or delete it first.' }, [], [Btn('Close'), Btn('Disable Spanish', 'primary')])
if (x === 'saved') return [Toast('success', 'circle-check', 'French added', 'Writers can now create French versions.')]
return null
}
// ---- setup
const SUV = FX.SETUP, SUN = SUV.reduce((a, r) => a + r.n, 0)
const suVal = (r, chosen) => r.name ? r.name + ' · ' + r.tag : chosen ? 'English · en' : 'Choose a language'
const suBody = () => {
if (x === 'empty') return Page('Languages', 'Content languages for Gallery.', null, Card(S('font-weight:600;' + HL, 'Confirm the default language'), S(TO, 'Gallery has no articles yet. New articles will be written in this language.'), Fd('Default content language', 'English (United Kingdom) · en-GB', { sel: true, help: 'Seeded from site locale en-GB. The full locale is kept. Confirm or change it.' }), D('display:flex', Btn('Save', 'primary'))))
const done = x === 'done', busy = x === 'applying', fail = x === 'fail', chosen = x !== 'ready'
return Page('Set article languages', 'Existing articles don\'t have a language yet. Confirm or change the language for each site.', null,
done ? Alert('success', 'circle-check', 'Languages assigned', SUN + ' articles now have a language. Nothing was translated, and no articles were grouped.', Btn('Open Articles', 'secondary', { sm: true })) :
fail ? Alert('error', 'circle-x', 'Nothing was changed', 'The update failed, so no article has a language yet. Your choices are kept. Try again; running it twice is safe.', Btn('Try again', 'secondary', { sm: true })) :
busy ? Alert('info', 'loader-circle', 'Assigning languages', 'Articles created while this runs get the site\'s default language.') :
Alert('info', 'info', 'This labels articles. It doesn\'t translate them.', 'Each existing article becomes the only version of its own story. Each site\'s language is seeded from its locale with the region kept: pt-BR stays Portuguese (Brazil). Nothing is applied until you confirm.'),
D('background:var(--ui-bg);border:1px solid var(--ui-border)', ...SUV.map(r => D(ROW + ';' + (m ? 'flex-wrap:wrap' : ''), D('flex:1;min-width:140px;display:flex;flex-direction:column', S('font-weight:600;' + HL, r.site), S(MU + ';font-size:12px', r.n + ' articles · ' + r.note)), D('width:' + (m ? '100%' : '300px'), Fd('', suVal(r, chosen), { sel: !done, val: !!(r.name || chosen), help: done ? null : r.name ? 'Seeded from site locale ' + r.locale + '. Confirm or change.' : chosen ? null : 'Required before you can apply.' }))))),
!done && D('display:flex;gap:8px;align-items:center;flex-wrap:wrap', Btn(busy ? 'Assigning…' : 'Confirm and assign to ' + SUN + ' articles', 'primary', { busy, dis: busy || x === 'ready' }), S(MU + ';font-size:12px', 'Counts are articles in ' + SUV.length + ' sites.')))
}
// ---- list
const FXS = FX.STORIES, LN = { en: 'English', fr: 'French', de: 'German', ar: 'Arabic', es: 'Spanish' }
const upMap = v => Object.fromEntries(Object.entries(v).map(([k, q]) => [k.toUpperCase(), q]))
const Cov = (map, o = {}) => D('display:flex;gap:6px;flex-wrap:wrap', ...(o.langs || FX.ENABLED.map(k => k.toUpperCase()).concat(map.ES ? ['ES'] : [])).filter(k => !o.hideMissing || map[k]).map(k => { const q = map[k], st = q && q[0], nr = q && q[1]; return A('span', { 'aria-label': k + ': ' + (st === 'pub' ? 'published' : st === 'draft' ? 'draft' : 'no version') + (nr ? ', needs review' : '') + (k === 'ES' ? ', language disabled' : '') }, 'display:inline-flex;align-items:center;gap:4px;height:22px;padding:0 6px;font:500 12px \'IBM Plex Mono\',monospace;' + (st ? 'border:1px solid var(--ui-border-accented);' + HL : 'border:1px dashed var(--ui-border-accented);' + MU), k, st === 'pub' ? Ic('circle-check', 12) : st === 'draft' ? Ic('pencil', 12) : Ic('minus', 12), nr && Ic('refresh-cw', 12)) }))
const Pager = (total, unitName, pages, size = 5) => D('display:flex;align-items:center;gap:12px;flex-wrap:wrap;' + (m ? 'justify-content:space-between' : ''), S(MU + ';font-size:12px;' + (m ? 'flex:1 1 100%' : 'flex:1'), 'Showing 1–' + Math.min(size, total) + ' of ' + total + ' ' + unitName),
D('display:flex;align-items:center;gap:8px;' + MU + ';font-size:12px', 'Rows', D('', Fd('', String(size), { sel: true }))),
D('display:flex;align-items:center;gap:6px', Btn('', 'secondary', { ic: 'chevron-left', al: 'Previous page', dis: true, sm: !m }), S(HL + ';font-weight:500;padding:0 6px', m ? 'Page 1 of ' + pages : '1 of ' + pages), Btn('', 'secondary', { ic: 'chevron-right', al: 'Next page', dis: pages < 2, sm: !m })))
const Chip = l => D('display:inline-flex;align-items:center;gap:6px;height:' + (m ? 36 : 24) + 'px;padding:0 8px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);' + HL + ';font-size:12px', l, A('span', { role: 'button', 'aria-label': 'Remove filter ' + l }, 'display:inline-flex', Ic('x', 14)))
const AT = [{ t: 'A complete guide to renovating a ground-floor kitchen and bathroom in a listed building, with every cost itemised for owners and tenants', v: { EN: ['pub', 0], 'EN-GB': ['draft', 0], FR: ['draft', 1], DE: ['pub', 0], 'PT-BR': ['draft', 0], 'ZH-HANS': ['pub', 0], JA: ['pub', 0], AR: ['draft', 0] }, by: 'Marta Alves', upd: '2 h ago' }, { t: 'Heat pumps for stone farmhouses', v: { EN: ['pub', 0], 'EN-GB': ['pub', 0], FR: ['draft', 0], DE: ['pub', 0], JA: ['draft', 0] }, by: 'Luis Ferrer', upd: 'Yesterday' }, { t: 'Sourcing reclaimed oak beams', v: { EN: ['draft', 0], 'EN-GB': ['draft', 0] }, by: 'Camille Roche', upd: '14 Oct' }]
const liPage = () => {
const press = x === 'emptyMissing', atel = x === 'long', lim = x === 'limited', fail = x === 'listFail', none = x === 'noResults', load = x === 'frLoading', draftF = x === 'filters'
const missView = x === 'frMissing' || x === 'tabsScroll' || press, frView = x === 'fr' || load || draftF || missView, active = draftF || atel
const DS = press ? FX.PRESS : FXS, isD = q => q && q[0] === 'draft'
const frV = DS.filter(s => s.v.fr && (!draftF || isD(s.v.fr))), frM = DS.filter(s => !s.v.fr && (!draftF || Object.values(s.v).some(isD)))
let tabs
if (frView) tabs = [['All', frV.length], ['Published', frV.filter(s => s.v.fr[0] === 'pub').length], ['Drafts', frV.filter(s => isD(s.v.fr)).length], ['Needs review', frV.filter(s => s.v.fr[1]).length], ['Not in French', frM.length]]
else if (atel) tabs = [['All stories', 3], ['Needs translation', 3], ['Needs review', 1]]
else if (none) tabs = [['All stories', 0], ['Needs translation', 0], ['Needs review', 0]]
else if (lim) tabs = [['All stories', 3], ['Needs translation', 3], ['Needs review', 0]]
else tabs = [['All stories', DS.length], ['Needs translation', DS.filter(s => FX.ENABLED.some(k => !s.v[k])).length], ['Needs review', DS.filter(s => Object.values(s.v).some(q => q[1])).length]]
if (load || fail) tabs = tabs.map(q => [q[0], '–'])
const tabOn = missView ? 4 : 0, site = press ? 'Press' : atel ? 'Atelier' : 'Journal'
const filt = () => D('display:flex;gap:8px;flex-wrap:wrap;align-items:center', D('flex:1;min-width:' + (m ? '0' : '220px'), Fd('', none ? 'zinc' : frView ? 'Search French titles' : 'Search titles in every language', { val: none })),
m ? Btn('Filters' + (active ? ' · 3' : ''), 'secondary', { ic: 'sliders-horizontal', al: active ? 'Filters, 3 active' : 'Filters' }) : [Fd('', site, { sel: true }), Fd('', frView ? 'French' : 'All languages', { sel: true }), Fd('', active ? 'Draft' : 'Any status', { sel: true }), Fd('', 'Any author', { sel: true }), Fd('', atel ? 'SEO: Needs attention' : 'Any SEO state', { sel: true })])
const chips = active && D('display:flex;gap:6px;flex-wrap:wrap', ...(draftF ? [Chip('Site: Journal'), Chip('Language: French'), Chip('Status: Draft')] : [Chip('Site: Atelier'), Chip('Status: Draft'), Chip('SEO: Needs attention')]), Btn('Clear all', 'ghost', { sm: true }))
const shift = x === 'tabsScroll' ? -96 : 0
const tabRow = D('position:relative;border-bottom:1px solid var(--ui-border);display:flex;align-items:center;gap:4px', m && Btn('', 'ghost', { ic: 'chevron-left', sm: true, al: 'Scroll tabs left', dis: !shift }),
A('div', { role: 'tablist', 'aria-label': 'Article views' }, 'flex:1;min-width:0;overflow:hidden;display:flex;gap:' + (m ? 16 : 24) + 'px;position:relative', D('display:flex;gap:' + (m ? 16 : 24) + 'px;flex-shrink:0;transform:translateX(' + (m ? shift : 0) + 'px)', ...tabs.map((q, i) => A('div', { role: 'tab', 'aria-selected': i === tabOn }, 'padding:' + (m ? '12px 0' : '8px 0') + ';white-space:nowrap;font-weight:' + (i === tabOn ? 600 : 500) + ';color:' + (i === tabOn ? 'var(--ui-text-highlighted)' : 'var(--ui-text-toned)') + ';border-bottom:2px solid ' + (i === tabOn ? 'var(--ui-primary)' : 'transparent'), q[0] + ' ', S(MU + ';font-weight:400', q[1])))), m && D('position:absolute;right:0;top:0;bottom:0;width:28px;background:linear-gradient(90deg,transparent,var(--ui-bg))'), m && shift && D('position:absolute;left:0;top:0;bottom:0;width:28px;background:linear-gradient(270deg,transparent,var(--ui-bg))')), m && Btn('', 'ghost', { ic: 'chevron-right', sm: true, al: 'Scroll tabs right' }))
const Row = (title, sub, right, lg) => D(ROW + ';' + (m ? 'flex-wrap:wrap' : ''), D('flex:1;min-width:0;display:flex;flex-direction:column;gap:2px', A('span', { lang: lg }, HL + ';font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap', title), sub && S(MU + ';font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap', sub)), right)
const Story = (title, sub, map, o, by, upd) => D(ROW + ';' + (m ? 'flex-direction:column;align-items:stretch;gap:6px' : ''), D('flex:1;min-width:0;display:flex;flex-direction:column;gap:2px', S(HL + ';font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap', title), sub && S(MU + ';font-size:12px', sub)), D((m ? '' : 'width:230px;') + 'flex-shrink:0', Cov(map, o)), m ? S(MU + ';font-size:12px', by + ' · ' + upd) : S(MU + ';width:120px', by), !m && S(MU + ';width:90px', upd))
const card = 'background:var(--ui-bg);border:1px solid var(--ui-border)', head = !m && D('display:flex;gap:12px;padding:8px 16px;background:var(--ui-bg-elevated);' + MU + ';font-weight:500', S('flex:1', 'Story'), S('width:230px', 'Languages'), S('width:120px', 'Author'), S('width:90px', 'Updated'))
let body, total = 0, unit = '', pages = 1
if (fail) body = Alert('error', 'circle-x', 'Couldn\'t load articles', 'Your filters and page are kept. Try again.', Btn('Try again', 'secondary', { sm: true }))
else if (load) body = Sk(5)
else if (press) body = D('padding:48px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;text-align:center;' + card, Ic('circle-check', 28), S('font-weight:600;' + HL, 'Every story you can see has a French version'), S(TO + ';max-width:360px', 'Stories without a French version will appear here.'))
else if (none) body = D('padding:48px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;text-align:center;' + card, Ic('search', 28), S('font-weight:600;' + HL, 'No stories match "zinc"'), S(TO, 'Search covers titles in every language you can read.'), Btn('Clear search', 'secondary', { sm: true }))
else if (missView) { const rep = s => ['en', 'fr', 'de', 'ar', 'es'].find(k => s.v[k]); body = D(card, ...frM.map(s => { const k = rep(s), q = s.v[k]; return Row(s.title, 'Opens ' + LN[k] + ' · ' + (q[0] === 'pub' ? 'published' : 'draft') + (k === 'es' ? ' · language disabled' : ''), Btn(m ? '' : 'Add French translation', 'secondary', { sm: true, ic: 'plus', al: 'Add French translation to ' + s.title })) })); total = frM.length; unit = 'stories with no French version you can read' }
else if (frView) { body = D(card, ...frV.map(s => Row(s.tr.fr, m ? s.by + ' · ' + s.upd : null, D('display:flex;gap:4px;flex-wrap:wrap;justify-content:flex-end', Pub(s.v.fr[0]), s.v.fr[1] ? Fr('Needs review') : null, !m && S(MU + ';width:120px;margin-left:12px', s.by), !m && S(MU + ';width:80px', s.upd)), 'fr'))); total = frV.length; unit = 'French versions' }
else if (atel) { body = D(card, head, ...AT.map(r => Story(r.t, null, r.v, { langs: ['EN', 'EN-GB', 'FR', 'DE', 'PT-BR', 'ZH-HANS', 'JA', 'AR'] }, r.by, r.upd))); total = 3; unit = 'stories' }
else { const rows = lim ? [FXS[1], FXS[2], FXS[3]] : FXS.slice(0, 5); body = D(card, head, ...rows.map(s => Story(s.title, s.v.es && !s.v.en ? 'Spanish only · language disabled on Journal' : null, lim ? { EN: s.v.en } : upMap(s.v), { hideMissing: lim }, s.by, s.upd))); total = lim ? 3 : FXS.length; unit = lim ? 'stories you can read' : 'stories'; pages = lim ? 1 : Math.ceil(total / 5) }
return D('flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column;gap:' + (m ? 12 : 16) + 'px;padding:' + (m ? '16px 16px 16px' : '24px') + ';position:relative', D('display:flex;align-items:center;gap:12px', S('flex:1;font:600 20px/28px \'IBM Plex Sans\',sans-serif;' + HL, 'Articles'), Btn(m ? '' : 'New article', 'primary', { ic: 'plus', al: 'New article' })), lim && Alert('neutral', 'lock', 'Showing only what you can read', 'Languages, counts and missing-translation results reflect versions you have permission to see.'), filt(), chips, tabRow, body, total ? Pager(total, unit, pages) : null)
}
const sheet = () => D('position:absolute;inset:0;z-index:50;background:var(--rc-overlay);display:flex;align-items:' + (m ? 'flex-end' : 'center') + ';justify-content:center', A('div', { role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Filters' }, 'width:100%;max-width:' + (m ? '100%' : '420px') + ';max-height:92%;background:var(--ui-bg);display:flex;flex-direction:column', D('display:flex;justify-content:center;padding:8px 0 0', S('width:36px;height:4px;background:var(--ui-border-accented)')),
D('padding:16px 16px 0;display:flex;align-items:center', S('flex:1;font:600 16px/24px \'IBM Plex Sans\',sans-serif;' + HL, 'Filters'), Btn('', 'ghost', { ic: 'x', al: 'Close filters' })),
D('padding:16px;display:flex;flex-direction:column;gap:14px;overflow-y:auto;min-height:0', Fd('Site', 'Journal', { sel: true }), Fd('Language', 'French', { sel: true }), Fd('Status', 'Draft', { sel: true }), Fd('Author', 'Any author', { sel: true }), Fd('SEO', 'Any SEO state', { sel: true })),
D('padding:12px 16px;border-top:1px solid var(--ui-border);display:flex;gap:8px', Btn('Clear all', 'secondary'), D('flex:1;display:flex', Btn('Show 2 results', 'primary', { w: '100%' })))))
// ---- new article
const naOv = () => {
const multi = x !== 'single'
return Dlg({ title: 'New article', desc: x === 'translation' ? 'Create a translation of a story you can read.' : 'Give the article a title. You can write the rest in the editor.' }, [
x === 'translation' ? [Fd('Story', 'Roof inspection checklist', { sel: true, help: 'No French version yet. Copies from English · published' }), Fd('Language', 'French', { sel: true }), Fd('Title', 'Liste de contrôle pour l\'inspection de la toiture', { focus: true, lang: 'fr' })] :
[Fd('Title', x === 'sitechange' ? 'Spring newsletter' : 'Title', { val: x === 'sitechange', focus: x !== 'sitechange', help: '0 / 200' }), Fd('Site', x === 'sitechange' ? 'Notes' : 'Journal', { sel: true }),
multi && Fd('Language', 'English', { sel: true, help: x === 'sitechange' ? 'Notes only offers English, so the language was reset from French.' : 'Default for Journal.' }),
multi && D('display:flex;flex-direction:column;gap:8px', Radio('New story', 'Starts a story in this language.', true), Radio('Translation of an existing story', 'Uses the same rules as Add translation.', false))]
], [Btn('Cancel'), Btn(x === 'translation' ? 'Create French draft' : 'Create article', 'primary')])
}
// ---- add translation
const FRT = S1.tr.fr, FRT5 = 'Liste de contrôle pour l\'inspection de la toiture'
const LS = st => LV('en', 'English', 'EN', st)
const atOv = () => {
const empty = x === 'empty', long = x === 'long', dupCase = x === 'dup' || x === 'dupHidden', st = dupCase ? S1 : S5, ft = dupCase ? FRT : FRT5, rev = st.v.en[2]
const f = [Fd('Language', 'French', { sel: true }), Fd('Source version', 'English · published · revision ' + rev, { sel: true, help: 'You choose which version to copy from. It is also the source tracked for review. Access is checked again when you create the draft.' }), Fd('Title in French', long ? ft + ' avec les coûts cachés derrière les carreaux, sous la baignoire et autour des canalisations anciennes' : ft, { focus: x === 'form', lang: 'fr', help: 'Copying text is a starting point: it stays in English until you translate it.' }),
D('display:flex;flex-direction:column;gap:8px', S('font-weight:500;' + HL, 'Starting content'), Radio('Start empty', 'Only the title. Freshness shows No source until you pick a tracked source.', empty), Radio('Copy from English', 'Copies body, excerpt, search and social text, image alternative text and authors. Images are reused, files aren\'t duplicated. The version starts as Needs review.', !empty))]
if (long) f.push(Alert('neutral', 'info', 'Authors', 'Marta Alves, Camille Roche, Luis Ferrer, Ana Pereira, Jonas Weber and 4 more will be copied. You can change them in the editor.'), Alert('neutral', 'info', 'Images', 'Cover image and 7 inline images are reused. Alternative text is copied and stays in English until you translate it.'))
if (x === 'dup') return Dlg({ title: 'Add a translation' }, [Alert('error', 'circle-x', 'French already exists for this story', 'Open it to continue. Your entries are kept here.', Btn('Open French version', 'secondary', { sm: true })), ...f], [Btn('Cancel'), Btn('Create French draft', 'primary', { dis: true })])
if (x === 'dupHidden') return Dlg({ title: 'Add a translation' }, [Alert('error', 'circle-x', 'French can\'t be created for this story', 'Try a different language, or ask an owner. Your entries are kept.'), ...f], [Btn('Cancel'), Btn('Create French draft', 'primary', { dis: true })])
if (x === 'fail') return Dlg({ title: 'Add a translation' }, [Alert('error', 'circle-x', 'Couldn\'t create the translation', 'Your entries are kept. Try again.'), ...f], [Btn('Cancel'), Btn('Try again', 'primary')])
if (x === 'gone') return Dlg({ title: 'Add a translation' }, [Alert('warning', 'triangle-alert', 'The source isn\'t available', 'It was deleted or you can no longer read it. Choose another source you can read, or start empty.'), Fd('Source version', 'Choose a version', { sel: true, val: false }), Fd('Title in French', ft)], [Btn('Cancel'), Btn('Create French draft', 'primary', { dis: true })])
if (x === 'saving') return Dlg({ title: 'Add a translation' }, f, [Btn('Cancel', 'secondary', { dis: true }), Btn('Creating…', 'primary', { busy: true })])
return Dlg({ title: 'Add a translation', desc: 'Creates a French draft of "' + st.title + '". It isn\'t published.', top: long ? 24 : 60 }, f, [Btn('Cancel'), Btn('Create French draft', 'primary')])
}
// ---- publish / delete
const pdOv = () => ({
publish: () => Dlg({ title: 'Publish French?', desc: 'Only the French version goes online. English and German aren\'t affected.' }, [], [Btn('Cancel'), Btn('Publish French', 'primary')]),
unpublish: () => Dlg({ title: 'Unpublish French?', desc: 'The French version goes back to draft and stops being public. English stays online.' }, [], [Btn('Cancel'), Btn('Unpublish French', 'primary')]),
delete: () => Dlg({ title: 'Delete the French version?', desc: '"' + FRT + '" will be deleted. Other versions stay.' }, [], [Btn('Cancel'), Btn('Delete French version', 'danger')]),
deleteSource: () => Dlg({ title: 'Delete the English version?', desc: 'English is the tracked source for versions you can read.' }, [Alert('warning', 'triangle-alert', 'Review tracking will pause', 'Versions that track English stay as they are. Their review history is kept, and they ask you to choose a new source.')], [Btn('Cancel'), Btn('Delete English version', 'danger')]),
deleteLast: () => Dlg({ title: 'Delete the only version?', desc: 'English is the only version of this story. Deleting it deletes the story.' }, [], [Btn('Cancel'), Btn('Delete article', 'danger')]),
saveAndPublish: () => Dlg({ title: 'Save and publish French?', desc: 'Your unsaved changes to the French version are saved first, then only the French version goes online. English and German aren\'t affected.' }, [], [Btn('Cancel'), Btn('Save and publish French', 'primary')]),
saveFailPublish: () => [Toast('error', 'circle-x', 'Couldn\'t save French', 'Nothing was published. Your changes are kept. Try again.')],
deleteStory: () => Dlg({ title: 'You can\'t delete this story', desc: 'Deleting a story needs delete permission on every version. You can still delete the version you are editing.' }, [], [Btn('Close'), Btn('Delete French version', 'danger')]),
fail: () => [Toast('error', 'circle-x', 'Couldn\'t publish French', 'Nothing changed. Try again.')],
done: () => [Toast('success', 'circle-check', 'French published', 'Other versions weren\'t changed.')]
}[x]?.())
// ---- freshness
const frOv = () => ({
review: () => Dlg({ title: 'Mark French as reviewed?', desc: 'This records that French revision 5 matches English revision 8 (14 Oct 2026). It doesn\'t change publication.' }, [Alert('neutral', 'info', 'Changed fields', 'Title, Body. Authors and the cover image don\'t count.')], [Btn('Cancel'), Btn('Mark as reviewed', 'primary')]),
changed: () => Dlg({ title: 'English changed again', desc: 'English is now at revision 9. You were reviewing revision 8.' }, [Alert('warning', 'triangle-alert', 'Review not recorded', 'Check the latest changes first. Nothing was marked.')], [Btn('Cancel'), Btn('Show source reference', 'secondary'), Btn('Review revision 9', 'primary')]),
changedTr: () => Dlg({ title: 'French changed while you were reviewing', desc: 'Camille Roche saved French revision 6 after you opened this.' }, [Alert('warning', 'triangle-alert', 'Review not recorded', 'This changed since you opened it. Review again.')], [Btn('Cancel'), Btn('Reload French', 'primary')]),
replace: () => Dlg({ title: 'Choose a tracked source', desc: 'Pick a version of this story you can read. French will show Needs review until you review it against the new source.' }, [Radio('English', 'Published · revision 8', false), Radio('German', 'Published · revision 3', true), Alert('error', 'circle-x', 'German can\'t be the source', 'It tracks French as its source. A source can\'t track back to this version.')], [Btn('Cancel'), Btn('Use German as source', 'primary', { dis: true })]),
done: () => [Toast('success', 'circle-check', 'French marked as reviewed', 'Reviewed against English revision 8.')],
fail: () => [Toast('error', 'circle-x', 'Couldn\'t mark as reviewed', 'Nothing was recorded. Try again.')]
}[x]?.())
// ---- permissions / conflicts
const pmOv = () => ({
session: () => Dlg({ title: 'Your session expired', desc: 'Log in to continue. Your title, body and search fields for this French version are kept in this browser and restored after you log in, with a notice.' }, [], [Btn('Log in', 'primary')]),
conflict: () => Dlg({ title: 'Someone else saved this version', desc: 'Camille Roche saved French revision 6 at 14:32. Your changes aren\'t saved. They stay here until you discard them.', w: 520 }, [Alert('neutral', 'info', 'Save over theirs', 'Replaces the title, body and search fields of the French version only. Other languages aren\'t changed. If someone saves again first, you\'ll be asked again.')], [Btn('Keep my changes'), Btn('Download my version', 'secondary', { ic: 'download' }), Btn('Reload theirs…'), Btn('Save over theirs', 'danger')]),
move: () => Dlg({ title: 'Move the story to Notes?', desc: 'This moves every version together.' }, [Fd('Site', 'Notes', { sel: true }), Alert('warning', 'triangle-alert', 'Notes doesn\'t enable every language in this story', 'Enable the missing languages on Notes first. Disabled-language versions count too. Nothing is split.')], [Btn('Cancel'), Btn('Move story', 'primary', { dis: true })]),
moveSlug: () => Dlg({ title: 'Move the story to Archive?', desc: 'All versions move together.' }, [Fd('Site', 'Archive', { sel: true }), Alert('error', 'circle-x', 'A French address is already used on Archive', 'Change the French address before moving.', Btn('Edit French address', 'secondary', { sm: true }))], [Btn('Cancel'), Btn('Move story', 'primary', { dis: true })]),
movePerm: () => Dlg({ title: 'You can\'t move this story' }, [Alert('error', 'circle-x', 'Moving needs edit permission on every version', 'Ask an owner to move it. Nothing was changed.')], [Btn('Close')])
}[x]?.())
// ---- compose
const EDV = { v: 'ed' }
if (v === 'sl') { Object.assign(shell, setS, x === 'single' ? { crumbs: ['Settings', 'Sites', 'Archive', 'Languages'], mTitle: 'Archive · Languages' } : {}); main = D('flex:1;min-height:0;display:flex;flex-direction:column;position:relative', slPage()); ov = [].concat(slOv() || []) }
else if (v === 'su') { Object.assign(shell, setS, { crumbs: ['Settings', 'Languages'] }); main = D('flex:1;min-height:0;display:flex;flex-direction:column', suBody()) }
else if (v === 'li') { Object.assign(shell, liS); main = D('flex:1;min-height:0;display:flex;flex-direction:column;position:relative', liPage()); if (x === 'filters' && m) ov = [sheet()] }
else if (v === 'na') { Object.assign(shell, liS); main = D('flex:1;min-height:0;display:flex;flex-direction:column;position:relative', liPage()); ov = [naOv()] }
else if (v === 'seo' || v === 'api') {
Object.assign(shell, edS)
let mainEl
if (v === 'seo') {
const unset = x === 'unset'
const rows = [['Address (slug)', 'comment-nous-chiffrons-une-renovation', 'Unique within Journal and French. Changing the title never changes it. Edit address changes it on purpose. Published: "Links to the old address will stop working. RenewCMS does not create redirects."'], ['Page title', FRT + ' | Journal', 'Empty uses the French title. Fallback: French title.'], ['Description', 'Empty', 'No fallback: the English site default isn\'t used for French, and the tag is left out.'], ['Image alternative text', 'Un plan de salle de bain avec les mesures', 'Written per language.']]
mainEl = Page('Search and sharing · French', 'Applies only to the French version. Page language: fr.', null, unset ? Alert('info', 'info', 'Public URL not configured', 'Add a public URL pattern for Journal to show page and canonical addresses. API links by article ID still work.', Btn('Open site settings', 'secondary', { sm: true })) : Card(D('display:flex;gap:8px;align-items:center;flex-wrap:wrap', Bd('Canonical', 'neutral', 'link'), S(mono + ';' + HL, 'https://journal.example.com/fr/comment-nous-chiffrons-une-renovation')), S(MU + ';font-size:12px', 'Built from the site\'s public URL pattern https://journal.example.com/{lang}/{slug}. Each language points to itself unless you set a canonical override.')), !unset && Card(S('font-weight:600;' + HL, 'Alternate languages'), S(TO, 'Published versions in enabled languages only.'), D('display:flex;gap:10px;align-items:center', S(mono, 'EN'), S('flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;' + TO, 'https://journal.example.com/en/how-we-price…'), Bd('Published', 'success', 'circle-check')), S(MU + ';font-size:12px', 'Drafts and the disabled Spanish language are left out.')), !unset && Card(S('font-weight:600;' + HL, 'Social metadata locale'), D('display:flex;gap:8px;align-items:center;flex-wrap:wrap', Bd('og:locale', 'neutral', 'share-2'), x === 'noSocial' ? S(MU, 'Not set') : S(mono + ';' + HL, 'fr_FR')), S(MU + ';font-size:12px;line-height:18px;text-wrap:pretty', x === 'noSocial' ? 'French has no region in its code and no social locale is set for it, so og:locale and og:locale:alternate are left out. An owner can set one in Journal languages.' : 'Set by an owner for French in Journal languages. Without it the tag would be left out: a region is never guessed from the language.')), ...rows.map(r => Fd(r[0], r[1], { help: r[2], val: r[1] !== 'Empty', mono: r[0].startsWith('Address') })))
} else mainEl = Page('Integration · Languages', 'Proposed. Not available in the current API.', null, Alert('warning', 'triangle-alert', 'Proposed capability', 'These requests don\'t exist yet. Existing article-ID requests are unchanged.'),
Card(S('font-weight:600;' + HL, 'Exact lookup: site + language + slug'), S(mono + ';' + HL, 'GET /v1/public/site/journal/lang/fr/article/{slug}'), S(TO, 'Matches the published French version with that slug, or returns 404 article_not_found. It never falls back, because a slug belongs to one language.'), S(mono + ';' + MU, '{ "story_id": "st_01", "lang": "fr", "slug": "…", "alternates": [{ "lang": "en", "slug": "…" }] }')),
Card(S('font-weight:600;' + HL, 'Resolve a story: story_id + language'), S(mono + ';' + HL, 'GET /v1/public/site/journal/story/{story_id}?lang=fr&fallback=default'), S(TO, 'Use the story_id from any response or from alternates[]. French published: 200, "lang": "fr". French missing or unpublished and fallback=default: 200 with the published default, "lang": "en", "requested_lang": "fr", "fallback": true. Default also missing or unpublished: 404 language_not_available. Without fallback=default a missing French is 404 language_not_available. No other language is tried, and drafts are never returned.')),
Card(S('font-weight:600;' + HL, 'Existing, unchanged'), S(mono + ';' + HL, 'GET /v1/article/{id}'), S(TO, 'Article-ID endpoints and links keep working. Each id is one language version.')))
main = D('flex:1;min-height:0;display:flex;flex-direction:column;position:relative', mainEl)
} else {
Object.assign(shell, edS)
const rtl = x === 'rtl', en = x === 'navFail'
let L = rtl ? AR : en ? EN : FR
const o = { cap: CAPS.full, pub: en ? 'pub' : 'draft', seoNote: false }
let extra = null
const popLeft = m ? 0 : 292
const pop = (rows, q = {}) => ov.push(LangPop(rows, { left: popLeft, top: 188, ...q }))
if (v === 'ed') {
o.seoNote = !m && !['menu', 'many', 'two'].includes(x)
if (x === 'menu') { o.open = 1; pop(MENUS.all) } else if (x === 'two') { o.open = 1; pop(MENUS.two) } else if (x === 'many') { o.open = 1; pop(MENUS.many, { find: 1, max: 520 }) }
else if (x === 'ro') o.cap = CAPS.ro
else if (x === 'roMenu') { o.cap = CAPS.ro; o.open = 1; pop(MENUS.ro) }
else if (x === 'p2') o.cap = CAPS.p2
else if (x === 'p3') o.cap = CAPS.p3
else if (x === 'p4') { o.open = 1; pop(MENUS.p4) }
else if (x === 'p5') { o.open = 1; pop(MENUS.p5) }
else if (x === 'p6') { o.cap = CAPS.p6; ov.push(D('position:absolute;z-index:40;' + (m ? 'left:0;right:0;bottom:0' : 'right:24px;top:188px;width:300px') + ';background:var(--ui-bg);border:1px solid var(--ui-border);box-shadow:var(--rc-shadow-pop);display:flex;flex-direction:column', A('div', { role: 'menu', 'aria-label': 'More actions' }, 'display:flex;flex-direction:column', A('div', { role: 'menuitem' }, 'padding:10px 12px;min-height:' + (m ? 48 : 36) + 'px;display:flex;align-items:center;color:var(--ui-error)', 'Delete French version'), A('div', { role: 'menuitem', 'aria-disabled': 'true' }, 'padding:10px 12px;display:flex;flex-direction:column;gap:2px;opacity:.6', 'Delete story', S(MU + ';font-size:12px', 'You can\'t delete this story.')), A('div', { role: 'menuitem', 'aria-disabled': 'true' }, 'padding:10px 12px;display:flex;flex-direction:column;gap:2px;opacity:.6', 'Move story', S(MU + ';font-size:12px', 'Needs edit permission on every version.'))))) }
else if (x === 'unsaved') { o.dirty = 1; ov.push(Dlg({ title: 'Leave without saving?', desc: 'Your changes to the French version aren\'t saved.' }, [], [Btn('Keep editing'), Btn('Discard and open English', 'danger'), Btn('Save and open English', 'primary')])) }
else if (x === 'saving') { o.dirty = 1; o.saving = 1 }
else if (x === 'saved') ov.push(Toast('success', 'circle-check', 'French saved', 'English wasn\'t changed.'))
else if (x === 'dirtyPublish') o.dirty = 1
else if (x === 'saveFail') { o.dirty = 1; o.saveFail = 1 }
else if (x === 'onlineDirty') { o.dirty = 1; o.pub = 'pub' }
else if (['invalid', 'titleSaving', 'failed', 'focus', 'dirty'].includes(x)) { o.titleState = { titleSaving: 'saving' }[x] || x; o.dirty = x !== 'focus'; if (x === 'invalid') { o.titleVal = ''; o.saveBlock = 1 } if (x === 'titleSaving') o.saving = 1; if (x === 'failed') o.saveFail = 1 }
else if (x === 'loading') o.loading = 1
else if (x === 'gone') { main = Page('This version isn\'t available', 'It may have been deleted, or you may not have access.', null, Btn('Back to Articles', 'secondary')) }
else if (x === 'navFail') ov.push(Toast('error', 'circle-x', 'Couldn\'t open the French version', 'You\'re still editing English. Try again.'))
} else if (v === 'src') {
o.src = x !== 'closed' && x !== 'mobileClosed'; o.narrow = x === 'narrow'; o.long = x === 'long'; o.srcMob = x === 'mobileSrc' || x === 'unavail' && m; o.gone = x === 'unavail'
if (x === 'reopen') ov.push(Toast('info', 'info', 'Reference: English', 'Restored. Your place in both panes is kept.'))
if (x === 'changeRef') ov.push(D('position:absolute;z-index:40;' + (m ? 'left:0;right:0;bottom:0' : 'left:200px;top:210px;width:340px') + ';background:var(--ui-bg);border:1px solid var(--ui-border);box-shadow:var(--rc-shadow-pop);display:flex;flex-direction:column', A('div', { role: 'menu', 'aria-label': 'Reference version' }, 'display:flex;flex-direction:column', LRow({ n: 'English', c: 'EN', st: 'pub', cur: 1 }), LRow({ n: 'German', c: 'DE', st: 'pub' })), D('padding:10px 12px;border-top:1px solid var(--ui-border);' + MU + ';font-size:12px;line-height:18px', 'Only changes what you compare against. The tracked review source stays English. Use Change tracked source in the review banner to change that.')))
if (x === 'changeRef') { o.src = true }
} else if (v === 'fr') {
o.fresh = 'Needs review'
const buttons = [Btn('Show source reference', 'secondary', { sm: true }), CAPS.full.rv && Btn('Mark as reviewed', 'primary', { sm: true }), Btn('Change tracked source', 'ghost', { sm: true })].filter(Boolean)
const ban = (tone, ic, t, d, act) => Alert(tone, ic, t, d, act)
if (x === 'gone') { o.fresh = 'No source'; extra = ban('neutral', 'info', 'Source deleted', 'The tracked English version was deleted. Review history is kept. Choose a new source to track changes again.', Btn('Choose a new source', 'secondary', { sm: true })) }
else if (x === 'inaccessible') { o.fresh = 'Source unavailable'; extra = ban('warning', 'lock', 'The source isn\'t available to you right now', 'It may be restricted. Review history is kept. Mark as reviewed is off until you can read it again.', Btn('Choose a new source', 'ghost', { sm: true })) }
else { if (x === 'pub') o.pub = 'pub'; extra = ban('warning', 'triangle-alert', 'English changed after French was reviewed', 'French was reviewed against English revision 7 (12 Oct 2026). English is at revision 8 (14 Oct 2026). Changed fields: Title, Body.' + (x === 'pub' ? ' French is still published. Reviewing doesn\'t change publication.' : ''), buttons) }
ov = ov.concat(frOv() || [])
} else if (v === 'pd') { o.pub = x === 'unpublish' || x === 'done' ? 'pub' : 'draft'; if (x === 'saveAndPublish' || x === 'saveFailPublish') o.dirty = 1; if (x === 'saveFailPublish') o.saveFail = 1; ov = ov.concat(pdOv() || []) }
else if (v === 'at') { o.pub = 'pub'; o.cap = CAPS.full; L = LS(x === 'dup' || x === 'dupHidden' ? S1 : S5); ov = ov.concat(atOv()) }
else if (v === 'pm') { if (x === 'conflict') { o.dirty = 1 } ; ov = ov.concat(pmOv() || []) }
if (!main) main = o.loading ? D('flex:1;padding:24px;display:flex;flex-direction:column;gap:14px', Sk(6)) : Editor(L, o, extra)
}
return { srcPane: null, edBanner: null, shell, mob: m, main, ov: ov.length ? D('position:absolute;inset:0;pointer-events:none', ...ov.map(e => D('pointer-events:none', e))) : null }
}
}
</script>
</body>
</html>
@@ -0,0 +1,600 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<div style="width:{{ fw }};height:{{ fh }};position:relative;display:flex;flex-direction:{{ dir }};background:var(--ui-bg-muted);color:var(--ui-text);font-family:'IBM Plex Sans',sans-serif;font-size:13px;line-height:20px;overflow:hidden">
<sc-if value="{{ desk }}" hint-placeholder-val="{{ true }}">
<div style="width:224px;flex-shrink:0;background:var(--ui-bg);border-right:1px solid var(--ui-border);display:flex;flex-direction:column;padding:12px;gap:2px">
<div style="display:flex;align-items:center;gap:10px;height:40px;padding:0 6px;margin-bottom:10px"><svg width="26" height="26" viewBox="0 0 16 16" shape-rendering="crispEdges"><rect width="16" height="16" fill="#111"></rect><path fill="#fff" fill-rule="evenodd" d="M4 3h6v1h1v1h1v3h-1v1h-1v1h1v1h1v2h-2v-2h-1v-1H6v3H4zm2 2v3h4V5z"></path></svg><span style="font-weight:600;font-size:14px;color:var(--ui-text-highlighted)">RenewCMS</span></div>
<sc-for list="{{ nav }}" as="n" hint-placeholder-count="3">
<div data-act="nav-{{ n.key }}" style="display:flex;align-items:center;gap:10px;height:32px;padding:0 10px;background:{{ n.bg }};color:{{ n.fg }};font-weight:{{ n.fw }}"><span aria-hidden="true" style="display:inline-block;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ n.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ n.icon }}.svg') center / contain no-repeat"></span>{{ n.t }}<sc-if value="{{ n.badge }}"><span style="margin-left:auto;height:18px;padding:0 6px;display:inline-flex;align-items:center;background:var(--ui-info);color:var(--ui-bg);font:500 11px 'IBM Plex Sans',sans-serif">Update</span></sc-if><sc-if value="{{ n.chev }}"><span aria-hidden="true" style="margin-left:{{ n.chevML }};display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ n.chev }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ n.chev }}.svg') center / contain no-repeat"></span></sc-if></div><sc-if value="{{ n.sub }}"><div style="display:flex;flex-direction:column;gap:2px;margin-left:19px;border-left:1px solid var(--ui-border)"><span style="padding:6px 10px 2px 12px;font-size:11px;line-height:16px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--ui-text-muted)">Media</span><sc-for list="{{ n.subs }}" as="x" hint-placeholder-count="2"><div data-act="nav-{{ x.t }}" style="display:flex;align-items:center;gap:10px;height:32px;padding:0 10px 0 12px;background:{{ x.bg }};color:{{ x.fg }};font-weight:{{ x.fw }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ x.ic }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ x.ic }}.svg') center / contain no-repeat"></span>{{ x.t }}</div></sc-for><sc-if value="{{ n.hasSites }}"><span style="padding:6px 10px 2px 12px;font-size:11px;line-height:16px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--ui-text-muted)">Publishing</span></sc-if><sc-for list="{{ n.subs2 }}" as="x2" hint-placeholder-count="1"><div data-act="nav-{{ x2.t }}" style="display:flex;align-items:center;gap:10px;height:32px;padding:0 10px 0 12px;background:{{ x2.bg }};color:{{ x2.fg }};font-weight:{{ x2.fw }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ x2.ic }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ x2.ic }}.svg') center / contain no-repeat"></span>{{ x2.t }}</div></sc-for><sc-if value="{{ n.hasAccess }}"><span style="padding:6px 10px 2px 12px;font-size:11px;line-height:16px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--ui-text-muted)">Access</span></sc-if><sc-for list="{{ n.subs3 }}" as="x3" hint-placeholder-count="1"><div data-act="nav-{{ x3.t }}" style="display:flex;align-items:center;gap:10px;height:32px;padding:0 10px 0 12px;background:{{ x3.bg }};color:{{ x3.fg }};font-weight:{{ x3.fw }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ x3.ic }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ x3.ic }}.svg') center / contain no-repeat"></span>{{ x3.t }}</div></sc-for></div></sc-if>
</sc-for>
<sc-if value="{{ contentShow }}"><div style="display:flex;flex-direction:column;gap:2px;margin-top:8px"><span style="padding:6px 10px 2px;font-size:11px;line-height:16px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--ui-text-muted)">Content</span><sc-if value="{{ contentColl }}"><div style="display:flex;align-items:center;gap:10px;height:32px;padding:0 10px;color:var(--ui-text-toned)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/layers.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/layers.svg') center / contain no-repeat"></span><span style="flex:1">All content types</span><span style="font-size:12px;color:var(--ui-text-muted)">{{ contentCount }}</span><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevron-right.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevron-right.svg') center / contain no-repeat"></span></div></sc-if><sc-for list="{{ contentRows }}" as="c" hint-placeholder-count="3"><div data-act="nav-content-{{ c.key }}" title="{{ c.name }}" style="display:flex;align-items:center;gap:10px;height:32px;padding:0 10px;background:{{ c.bg }};color:{{ c.fg }};font-weight:{{ c.fw }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ c.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ c.icon }}.svg') center / contain no-repeat"></span><span style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ c.name }}</span></div></sc-for><sc-if value="{{ contentMore }}"><div style="display:flex;align-items:center;gap:10px;height:32px;padding:0 10px;color:var(--ui-text-muted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/ellipsis.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/ellipsis.svg') center / contain no-repeat"></span><span>{{ contentMore }}</span></div></sc-if></div></sc-if>
<div style="flex:1"></div>
<sc-if value="{{ s.boot }}"><div style="display:flex;align-items:center;gap:10px;height:40px;padding:0 6px"><span style="width:26px;height:26px;background:var(--ui-bg-accented)"></span><span style="width:80px;height:12px;background:var(--ui-bg-accented)"></span></div></sc-if>
<sc-if value="{{ notBoot }}"><div data-act="usermenu" role="button" aria-haspopup="menu" aria-expanded="{{ menuOpen }}" title="Account" style="display:flex;align-items:center;gap:10px;height:40px;padding:0 6px;background:{{ chipBg }};color:{{ chipFg }}"><span style="width:26px;height:26px;flex-shrink:0;background:var(--rc-sand);color:var(--rc-on-sand);display:flex;align-items:center;justify-content:center;font:600 11px 'IBM Plex Sans',sans-serif">{{ ini }}</span><span style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500">{{ who }}</span><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevrons-up-down.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevrons-up-down.svg') center / contain no-repeat"></span></div></sc-if>
</div>
<sc-if value="{{ deskMenu }}"><div role="menu" aria-label="Account" style="position:absolute;left:12px;bottom:56px;z-index:40;width:224px;background:var(--ui-bg);border:1px solid var(--ui-border);box-shadow:var(--rc-shadow-pop);padding:4px"><div style="padding:8px 8px 10px;display:flex;flex-direction:column;gap:2px;border-bottom:1px solid var(--ui-border);margin-bottom:4px"><span style="font-weight:600;color:var(--ui-text-highlighted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ who }}</span><span style="font-size:12px;color:var(--ui-text-muted)">marta@renew.example</span></div><div data-act="nav-profile" role="menuitem" style="height:32px;padding:0 8px;display:flex;align-items:center;gap:10px;background:{{ pfItemBg }};color:var(--ui-text-highlighted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/circle-user.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/circle-user.svg') center / contain no-repeat"></span>Profile</div><div style="height:1px;background:var(--ui-border);margin:4px 0"></div><div style="padding:6px 8px 2px;font-size:11px;font-weight:500;letter-spacing:.04em;text-transform:uppercase;color:var(--ui-text-muted)">Theme</div><sc-for list="{{ themeSeg }}" as="m" hint-placeholder-count="3"><div data-act="{{ m.act }}" role="menuitemradio" aria-checked="{{ m.on }}" style="height:32px;padding:0 8px;display:flex;align-items:center;justify-content:space-between;font-size:14px;color:var(--ui-text-highlighted)">{{ m.t }}<span aria-hidden="true" style="display:inline-block;width:16px;height:16px;background-color:currentColor;opacity:{{ m.op }};-webkit-mask:url('https://api.iconify.design/lucide/check.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/check.svg') center / contain no-repeat"></span></div></sc-for><div style="height:1px;background:var(--ui-border);margin:4px 0"></div><div style="padding:6px 8px 2px;font-size:11px;font-weight:500;letter-spacing:.04em;text-transform:uppercase;color:var(--ui-text-muted)">Language</div><sc-for list="{{ langSeg }}" as="m" hint-placeholder-count="2"><div data-act="{{ m.act }}" lang="{{ m.code }}" role="menuitemradio" aria-checked="{{ m.on }}" style="height:32px;padding:0 8px;display:flex;align-items:center;justify-content:space-between;font-size:14px;color:var(--ui-text-highlighted)">{{ m.t }}<span aria-hidden="true" style="display:inline-block;width:16px;height:16px;background-color:currentColor;opacity:{{ m.op }};-webkit-mask:url('https://api.iconify.design/lucide/check.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/check.svg') center / contain no-repeat"></span></div></sc-for><div style="height:1px;background:var(--ui-border);margin:4px 0"></div><div data-act="logout" role="menuitem" style="height:32px;padding:0 8px;display:flex;align-items:center;gap:10px;color:var(--ui-text-highlighted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/log-out.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/log-out.svg') center / contain no-repeat"></span>Log out</div></div></sc-if>
</sc-if>
<div style="flex:1;min-width:0;min-height:0;display:flex;flex-direction:column">
<sc-if value="{{ desk }}" hint-placeholder-val="{{ true }}">
<div style="height:48px;flex-shrink:0;border-bottom:1px solid var(--ui-border);background:var(--ui-bg);display:flex;align-items:center;padding:0 24px;gap:8px;color:var(--ui-text-muted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/panel-left-close.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/panel-left-close.svg') center / contain no-repeat"></span><span style="width:1px;height:16px;background:var(--ui-border);margin:0 6px"></span><sc-for list="{{ crumbs }}" as="c" hint-placeholder-count="1"><sc-if value="{{ c.sep }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevron-right.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevron-right.svg') center / contain no-repeat"></span></sc-if><span style="color:{{ c.fg }};font-weight:{{ c.fw }}">{{ c.t }}</span></sc-for></div>
</sc-if>
<sc-if value="{{ mob }}">
<div style="height:56px;flex-shrink:0;border-bottom:1px solid var(--ui-border);background:var(--ui-bg);display:flex;align-items:center;padding:0 6px;gap:4px;color:var(--ui-text-highlighted)"><span data-act="m-lead" style="position:relative;width:44px;height:44px;display:flex;align-items:center;justify-content:center"><sc-if value="{{ s.navBadge }}"><span aria-label="Update available" style="position:absolute;top:9px;right:9px;width:10px;height:10px;background:var(--ui-info);border:2px solid var(--ui-bg)"></span></sc-if><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:20px;height:20px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ mLead }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ mLead }}.svg') center / contain no-repeat"></span></span><span style="flex:1;min-width:0;font-weight:600;font-size:16px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ s.mTitle }}</span><sc-if value="{{ s.mAction }}"><span data-act="m-action" style="width:44px;height:44px;display:flex;align-items:center;justify-content:center"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:20px;height:20px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ s.mAction }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ s.mAction }}.svg') center / contain no-repeat"></span></span></sc-if></div>
</sc-if>
<sc-if value="{{ s.offline }}"><div role="status" style="flex-shrink:0;display:flex;align-items:center;gap:10px;padding:10px 16px;background:var(--ui-bg-inverted);color:var(--ui-text-inverted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/wifi-off.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/wifi-off.svg') center / contain no-repeat"></span><span style="flex:1">{{ offText }}</span></div></sc-if>
<div style="flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column">
<sc-if value="{{ isSlot }}">{{ slotBody }}</sc-if>
<sc-if value="{{ isBoot }}">
<div aria-busy="true" style="padding:{{ pad }};display:flex;flex-direction:column;gap:24px">
<div style="display:flex;flex-direction:column;gap:8px"><span style="width:220px;height:22px;background:var(--ui-bg-accented)"></span><span style="width:320px;max-width:80%;height:12px;background:var(--ui-bg-accented)"></span></div>
<div style="display:grid;grid-template-columns:{{ cols3 }};gap:12px"><span style="height:88px;background:var(--ui-bg-accented)"></span><span style="height:88px;background:var(--ui-bg-accented)"></span><span style="height:88px;background:var(--ui-bg-accented)"></span></div>
<span style="height:220px;background:var(--ui-bg-accented)"></span>
<span style="align-self:flex-start;height:20px;padding:0 6px;display:inline-flex;align-items:center;border:1px dashed var(--ui-border-accented);color:var(--ui-text-toned);font:500 11px 'IBM Plex Mono',monospace">API pending · GET /v1/auth/me</span>
</div>
</sc-if>
<sc-if value="{{ isHome }}">
<div style="padding:{{ pad }};display:flex;flex-direction:column;gap:{{ gap }};max-width:1040px">
<div style="display:flex;align-items:flex-end;gap:16px;flex-wrap:wrap"><div style="flex:1;min-width:200px;display:flex;flex-direction:column;gap:4px"><span style="font:600 20px/28px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">Good morning, {{ who }}</span><span style="color:var(--ui-text-muted);font-size:14px">{{ s.home.sub }}</span></div><sc-if value="{{ desk }}"><span data-act="new" style="height:32px;padding:0 12px;display:flex;align-items:center;gap:6px;background:var(--ui-primary);color:var(--ui-bg);font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/plus.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/plus.svg') center / contain no-repeat"></span>New article</span></sc-if></div>
<sc-if value="{{ s.home.banner }}"><div role="region" aria-label="Update available" style="display:flex;flex-direction:{{ upBtnDir }};align-items:{{ upBannerAlign }};gap:12px;padding:12px 14px;background:{{ s.home.banner.bg }};border:1px solid var(--ui-border)"><div style="flex:1;display:flex;gap:10px;min-width:0"><span style="display:flex;padding-top:2px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:{{ s.home.banner.fg }};-webkit-mask:url('https://api.iconify.design/lucide/{{ s.home.banner.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ s.home.banner.icon }}.svg') center / contain no-repeat"></span></span><div style="display:flex;flex-direction:column;gap:2px"><span style="font-weight:600;color:var(--ui-text-highlighted)">{{ s.home.banner.title }}</span><span style="color:var(--ui-text-toned);text-wrap:pretty">{{ s.home.banner.text }}</span></div></div><div style="display:flex;align-items:center;gap:8px"><span style="flex:1;height:{{ btnH }};padding:0 12px;display:flex;align-items:center;justify-content:center;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:var(--ui-text-highlighted);font-weight:500">{{ s.home.banner.btn }}</span><span role="button" aria-label="Dismiss" style="width:{{ btnH }};height:{{ btnH }};display:flex;align-items:center;justify-content:center;color:var(--ui-text-muted)"><span aria-hidden="true" style="display:inline-block;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/x.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/x.svg') center / contain no-repeat"></span></span></div></div></sc-if>
<sc-if value="{{ s.home.error }}"><div role="alert" style="display:flex;gap:10px;padding:12px 14px;background:var(--rc-error-soft);color:var(--ui-error)"><span style="display:flex;padding-top:2px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/circle-x.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/circle-x.svg') center / contain no-repeat"></span></span><div style="flex:1;display:flex;flex-direction:column;gap:8px"><div style="display:flex;flex-direction:column;gap:2px"><span style="font-weight:600">Couldn't load your articles</span><span style="color:var(--ui-text-toned)">Check your connection and try again.</span></div><span style="align-self:flex-start;height:28px;padding:0 10px;display:flex;align-items:center;gap:6px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:var(--ui-text-highlighted);font-weight:500;font-size:12px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/rotate-cw.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/rotate-cw.svg') center / contain no-repeat"></span>Try again</span></div></div></sc-if>
<sc-if value="{{ s.home.stats }}">
<div style="display:grid;grid-template-columns:{{ cols3 }};gap:12px">
<sc-for list="{{ s.home.stats }}" as="k" hint-placeholder-count="3"><div style="background:var(--ui-bg);border:1px solid var(--ui-border);padding:16px;display:flex;flex-direction:column;gap:4px"><sc-if value="{{ k.skel }}"><span style="width:40px;height:28px;background:var(--ui-bg-accented)"></span><span style="width:70px;height:12px;background:var(--ui-bg-accented)"></span></sc-if><sc-if value="{{ k.n }}"><span style="font:600 24px/32px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">{{ k.n }}</span><span style="color:var(--ui-text-muted)">{{ k.l }}</span></sc-if></div></sc-for>
</div>
</sc-if>
<sc-if value="{{ s.home.empty }}">
<div style="background:var(--ui-bg);border:1px solid var(--ui-border);padding:48px 24px;display:flex;flex-direction:column;align-items:center;gap:12px;text-align:center"><span style="width:44px;height:44px;display:flex;align-items:center;justify-content:center;background:var(--ui-bg-elevated);color:var(--ui-text-toned)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:20px;height:20px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/file-text.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/file-text.svg') center / contain no-repeat"></span></span><span style="font-weight:600;font-size:18px;color:var(--ui-text-highlighted)">Write your first article</span><span style="color:var(--ui-text-muted);max-width:340px;font-size:14px;line-height:22px">It starts as a draft, so nothing appears on your site until you publish it.</span><span data-act="new" style="height:{{ btnH }};padding:0 16px;display:flex;align-items:center;gap:6px;background:var(--ui-primary);color:var(--ui-bg);font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/plus.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/plus.svg') center / contain no-repeat"></span>New article</span></div>
</sc-if>
<sc-if value="{{ s.home.recent }}">
<div style="display:flex;flex-direction:column;gap:10px">
<div style="display:flex;align-items:baseline;justify-content:space-between"><span style="font:600 16px/24px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">Recently updated</span><span data-act="nav-articles" style="color:var(--ui-text-toned);text-decoration:underline;text-underline-offset:3px">All articles</span></div>
<div style="background:var(--ui-bg);border:1px solid var(--ui-border)">
<sc-for list="{{ s.home.recent }}" as="a" hint-placeholder-count="5">
<div data-act="open-{{ a.id }}" style="display:flex;align-items:center;gap:12px;min-height:{{ rowH }};padding:8px 12px;border-top:1px solid var(--ui-border-muted)">
<sc-if value="{{ a.skel }}"><span style="flex:1;height:12px;background:var(--ui-bg-accented);max-width:60%"></span><span style="width:56px;height:20px;background:var(--ui-bg-accented)"></span></sc-if>
<sc-if value="{{ a.t }}"><span style="flex:1;min-width:0;font-weight:500;color:var(--ui-text-highlighted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ a.t }}</span><sc-if value="{{ a.on }}"><span style="height:22px;padding:0 7px;display:inline-flex;align-items:center;gap:4px;background:var(--ui-bg-inverted);color:var(--ui-text-inverted);font-size:12px;font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:12px;height:12px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/globe.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/globe.svg') center / contain no-repeat"></span>Online</span></sc-if><sc-if value="{{ a.draft }}"><span style="height:22px;padding:0 7px;display:inline-flex;align-items:center;gap:4px;border:1px solid var(--ui-border-accented);color:var(--ui-text-toned);font-size:12px;font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:12px;height:12px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/pencil-line.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/pencil-line.svg') center / contain no-repeat"></span>Draft</span></sc-if><sc-if value="{{ desk }}"><span style="width:110px;text-align:right;color:var(--ui-text-muted)">{{ a.u }}</span></sc-if></sc-if>
</div>
</sc-for>
</div>
</div>
</sc-if>
<sc-if value="{{ act.show }}">
<div style="display:flex;flex-direction:column;gap:10px">
<div style="display:flex;align-items:baseline;justify-content:space-between"><span style="font:600 16px/24px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">{{ act.title }}</span><span data-act="nav-activity" style="color:var(--ui-text-toned);text-decoration:underline;text-underline-offset:3px">View all</span></div>
<sc-if value="{{ act.error }}"><div style="display:flex;gap:10px;padding:12px 14px;background:var(--rc-error-soft);color:var(--ui-error)"><span style="display:flex;padding-top:2px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/circle-x.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/circle-x.svg') center / contain no-repeat;"></span></span><div style="flex:1;display:flex;flex-direction:column;gap:8px"><div style="display:flex;flex-direction:column;gap:2px"><span style="font-weight:600">Couldn't load recent activity</span><span style="color:var(--ui-text-toned)">Check your connection and try again.</span></div><span role="button" style="height:28px;padding:0 10px;display:flex;align-items:center;justify-content:center;gap:6px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:var(--ui-text-highlighted);font-weight:500;font-size:12px;white-space:nowrap;opacity:1"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/rotate-cw.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/rotate-cw.svg') center / contain no-repeat;"></span>Try again</span></div></div></sc-if>
<sc-if value="{{ act.empty }}"><div style="background:var(--ui-bg);border:1px solid var(--ui-border);padding:32px 24px;display:flex;flex-direction:column;align-items:center;gap:6px;text-align:center"><span style="color:var(--ui-text-muted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:20px;height:20px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/history.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/history.svg') center / contain no-repeat;"></span></span><span style="font-weight:600;color:var(--ui-text-highlighted)">No activity yet</span><span style="color:var(--ui-text-muted);max-width:260px">{{ act.emptyText }}</span></div></sc-if>
<sc-if value="{{ act.rows }}"><div style="background:var(--ui-bg);border:1px solid var(--ui-border)"><sc-for list="{{ act.rows }}" as="r" hint-placeholder-count="5"><div style="display:flex;align-items:center;gap:12px;min-height:{{ rowH }};padding:8px 12px;border-top:1px solid var(--ui-border-muted)"><sc-if value="{{ r.skel }}"><span style="width:24px;height:24px;flex-shrink:0;background:var(--ui-bg-accented)"></span><span style="flex:1;height:12px;background:var(--ui-bg-accented);max-width:60%"></span></sc-if><sc-if value="{{ r.s }}"><span style="width:24px;height:24px;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--rc-sand);color:var(--rc-on-sand);font:600 10px 'IBM Plex Sans',sans-serif">{{ r.ini }}</span><div style="flex:1;min-width:0;display:flex;flex-direction:column"><span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ui-text)">{{ r.s }}<span style="font-weight:600;color:var(--ui-text-highlighted)">{{ r.em }}</span></span><span style="font-size:12px;color:var(--ui-text-muted)">{{ r.meta }}</span></div><sc-if value="{{ r.fail }}"><span style="height:20px;padding:0 6px;display:inline-flex;align-items:center;gap:4px;background:var(--rc-error-soft);color:var(--ui-error);font-size:12px;font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:12px;height:12px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/circle-x.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/circle-x.svg') center / contain no-repeat;"></span>Failed</span></sc-if></sc-if></div></sc-for></div></sc-if>
</div>
</sc-if>
<div style="background:var(--ui-bg);border:1px solid var(--ui-border);padding:16px;display:flex;gap:14px;align-items:flex-start"><span style="width:36px;height:36px;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--rc-sand);color:var(--rc-on-sand)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:18px;height:18px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/plug.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/plug.svg') center / contain no-repeat"></span></span><div style="flex:1;display:flex;flex-direction:column;gap:4px"><span style="font-weight:600;font-size:14px;color:var(--ui-text-highlighted)">Connect your website</span><span style="color:var(--ui-text-muted);text-wrap:pretty">Your site reads published articles from RenewCMS. Share this guide with whoever builds it.</span><span data-act="nav-integration" style="color:var(--ui-text-highlighted);font-weight:500;text-decoration:underline;text-underline-offset:3px;margin-top:4px">Open the guide</span></div></div>
</div>
</sc-if>
<sc-if value="{{ isList }}">
<div style="padding:{{ pad }};display:flex;flex-direction:column;gap:16px;flex:1;min-height:0">
<div style="display:flex;align-items:flex-end;gap:16px;flex-wrap:wrap"><div style="flex:1;min-width:200px;display:flex;flex-direction:column;gap:2px"><span style="font:600 20px/28px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">Articles</span><span style="color:var(--ui-text-muted)">Everything you've written, online and in draft.</span></div><sc-if value="{{ desk }}"><span data-act="new" style="height:32px;padding:0 12px;display:flex;align-items:center;gap:6px;background:var(--ui-primary);color:var(--ui-bg);font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/plus.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/plus.svg') center / contain no-repeat"></span>New article</span></sc-if></div>
<div style="display:flex;flex-direction:{{ tbDir }};gap:{{ tbGap }};border-bottom:{{ tbBorder }}">
<sc-if value="{{ mob }}"><span style="height:44px;padding:0 4px 0 12px;display:flex;align-items:center;gap:8px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:{{ qFg }};font-size:16px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/search.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/search.svg') center / contain no-repeat"></span><span style="flex:1">{{ qText }}</span><sc-if value="{{ s.list.q }}"><span style="width:36px;height:36px;display:flex;align-items:center;justify-content:center;color:var(--ui-text-muted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/x.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/x.svg') center / contain no-repeat"></span></span></sc-if></span></sc-if>
<sc-if value="{{ mob }}"><span role="switch" aria-checked="{{ naOn }}" style="display:flex;flex-direction:column"><span style="height:44px;padding:0 10px;display:flex;align-items:center;gap:8px;border:1px solid {{ naBd }};background:{{ naBg }};color:{{ naFg }};font-size:15px;font-weight:{{ naFw }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/lightbulb.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/lightbulb.svg') center / contain no-repeat"></span>Needs attention<span style="font:500 11px 'IBM Plex Mono',monospace">{{ naN }}</span></span></span></sc-if><sc-if value="{{ multiMob }}"><span style="position:relative;display:flex;flex-direction:column;"><span style="height:44px;padding:0 10px;display:flex;align-items:center;gap:8px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:{{ sfFg }};font-size:16px;"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/globe.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/globe.svg') center / contain no-repeat"></span><span style="flex:1">{{ sfText }}</span><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat"></span></span><sc-if value="{{ sfMenu }}"><div role="listbox" aria-label="Site" style="position:absolute;left:0;right:0;top:48px;z-index:5;background:var(--ui-bg);border:1px solid var(--ui-border);box-shadow:var(--rc-shadow-pop);padding:4px"><sc-for list="{{ sfOpts }}" as="o" hint-placeholder-count="3"><div role="option" aria-selected="{{ o.on }}" style="height:44px;padding:0 8px;display:flex;align-items:center;justify-content:space-between;background:{{ o.bg }};color:var(--ui-text-highlighted)">{{ o.t }}<span style="opacity:{{ o.op }};display:flex"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/check.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/check.svg') center / contain no-repeat"></span></span></div></sc-for></div></sc-if></span></sc-if>
<div style="display:flex;gap:20px;flex:1;border-bottom:{{ tabBorder }}"><sc-for list="{{ tabs }}" as="t" hint-placeholder-count="3"><span style="height:{{ tabH }};display:flex;align-items:center;gap:6px;border-bottom:2px solid {{ t.line }};margin-bottom:-1px;font-weight:{{ t.fw }};color:{{ t.fg }};font-size:{{ tabF }}">{{ t.t }}<span style="font:500 11px 'IBM Plex Mono',monospace;color:var(--ui-text-muted)">{{ t.n }}</span></span></sc-for></div>
<sc-if value="{{ desk }}"><span role="switch" aria-checked="{{ naOn }}" style="margin-bottom:6px;display:flex"><span style="height:32px;padding:0 10px;display:flex;align-items:center;gap:8px;border:1px solid {{ naBd }};background:{{ naBg }};color:{{ naFg }};font-size:13px;font-weight:{{ naFw }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/lightbulb.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/lightbulb.svg') center / contain no-repeat"></span>Needs attention<span style="font:500 11px 'IBM Plex Mono',monospace">{{ naN }}</span></span></span></sc-if><sc-if value="{{ desk }}"><span style="width:260px;height:32px;margin-bottom:6px;padding:0 4px 0 10px;display:flex;align-items:center;gap:8px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:{{ qFg }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/search.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/search.svg') center / contain no-repeat"></span><span style="flex:1">{{ qText }}</span><sc-if value="{{ s.list.q }}"><span style="width:24px;height:24px;display:flex;align-items:center;justify-content:center;color:var(--ui-text-muted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/x.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/x.svg') center / contain no-repeat"></span></span></sc-if></span></sc-if>
<sc-if value="{{ multiDesk }}"><span style="position:relative;display:flex;flex-direction:column;width:150px;margin-bottom:6px;"><span style="height:32px;padding:0 10px;display:flex;align-items:center;gap:8px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:{{ sfFg }};"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/globe.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/globe.svg') center / contain no-repeat"></span><span style="flex:1">{{ sfText }}</span><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat"></span></span><sc-if value="{{ sfMenu }}"><div role="listbox" aria-label="Site" style="position:absolute;left:0;right:0;top:36px;z-index:5;background:var(--ui-bg);border:1px solid var(--ui-border);box-shadow:var(--rc-shadow-pop);padding:4px"><sc-for list="{{ sfOpts }}" as="o" hint-placeholder-count="3"><div role="option" aria-selected="{{ o.on }}" style="height:32px;padding:0 8px;display:flex;align-items:center;justify-content:space-between;background:{{ o.bg }};color:var(--ui-text-highlighted)">{{ o.t }}<span style="opacity:{{ o.op }};display:flex"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/check.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/check.svg') center / contain no-repeat"></span></span></div></sc-for></div></sc-if></span></sc-if>
</div>
<sc-if value="{{ s.list.empty }}">
<div style="background:var(--ui-bg);border:1px solid var(--ui-border);padding:56px 24px;display:flex;flex-direction:column;align-items:center;gap:12px;text-align:center"><span style="width:44px;height:44px;display:flex;align-items:center;justify-content:center;background:var(--ui-bg-elevated);color:var(--ui-text-toned)"><span aria-hidden="true" style="display:inline-block;width:20px;height:20px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ s.list.empty.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ s.list.empty.icon }}.svg') center / contain no-repeat"></span></span><span style="font-weight:600;font-size:15px;color:var(--ui-text-highlighted)">{{ s.list.empty.title }}</span><span style="color:var(--ui-text-muted);max-width:280px">{{ s.list.empty.text }}</span><span data-act="empty" style="height:{{ btnH }};padding:0 12px;display:flex;align-items:center;font-weight:500;background:{{ emptyBg }};color:{{ emptyFg }};border:1px solid {{ emptyBd }}">{{ s.list.empty.action }}</span></div>
</sc-if>
<sc-if value="{{ pgErr }}"><div role="alert" style="display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--rc-error-soft);color:var(--ui-error)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/circle-x.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/circle-x.svg') center / contain no-repeat"></span><span style="flex:1;color:var(--ui-text-toned)">{{ pgErr }}</span><span data-act="pg-retry" style="height:{{ pgBtn }};padding:0 12px;display:flex;align-items:center;gap:6px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:var(--ui-text-highlighted);font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/rotate-cw.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/rotate-cw.svg') center / contain no-repeat"></span>Retry</span></div></sc-if>
<sc-if value="{{ pgNote }}"><div role="status" style="display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--rc-info-soft);color:var(--ui-info)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/info.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/info.svg') center / contain no-repeat"></span><span style="flex:1;color:var(--ui-text-toned)">{{ pgNote }}</span></div></sc-if>
<sc-if value="{{ showTable }}">
<div style="background:var(--ui-bg);border:1px solid var(--ui-border)">
<div style="display:grid;grid-template-columns:{{ gridCols }};height:36px;align-items:center;padding:0 12px;background:var(--ui-bg-elevated);color:var(--ui-text-muted);font-weight:500"><span>Title</span><sc-if value="{{ authorsCol }}"><span>Authors</span></sc-if><sc-if value="{{ multi }}"><span>Site</span></sc-if><span>Status</span><span>Created</span><span style="display:flex;align-items:center;gap:4px;color:var(--ui-text-highlighted)">Updated<span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:12px;height:12px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/arrow-down.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/arrow-down.svg') center / contain no-repeat"></span></span><span></span></div>
<sc-for list="{{ rows }}" as="a" hint-placeholder-count="6">
<div data-act="open-{{ a.id }}" style="display:grid;grid-template-columns:{{ gridCols }};height:44px;align-items:center;padding:0 12px;border-top:1px solid var(--ui-border);background:{{ a.bg }};opacity:{{ a.op }};position:relative">
<sc-if value="{{ a.skel }}"><span style="height:12px;width:60%;background:var(--ui-bg-accented)"></span><sc-if value="{{ authorsCol }}"><span style="width:60px;height:24px;background:var(--ui-bg-accented)"></span></sc-if><sc-if value="{{ multi }}"><span style="width:70px;height:12px;background:var(--ui-bg-accented)"></span></sc-if><span style="width:60px;height:20px;background:var(--ui-bg-accented)"></span><span style="width:80px;height:12px;background:var(--ui-bg-accented)"></span><span style="width:80px;height:12px;background:var(--ui-bg-accented)"></span><span></span></sc-if>
<sc-if value="{{ a.t }}">
<span style="font-weight:500;color:var(--ui-text-highlighted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding-right:12px">{{ a.t }}</span>
<sc-if value="{{ authorsCol }}"><span title="{{ a.auNames }}" style="display:flex;align-items:center;min-width:0"><sc-if value="{{ a.hasAu }}"><sc-for list="{{ a.avs }}" as="v" hint-placeholder-count="2"><span style="width:24px;height:24px;margin-left:{{ v.ml }};flex-shrink:0;display:flex;align-items:center;justify-content:center;background:{{ v.bg }};color:var(--ui-text-highlighted);border:2px solid var(--ui-bg);font:600 10px/1 'IBM Plex Sans',sans-serif">{{ v.i }}</span></sc-for><sc-if value="{{ a.auMore }}"><span style="margin-left:6px;font-size:12px;color:var(--ui-text-muted)">{{ a.auMore }}</span></sc-if></sc-if><sc-if value="{{ a.noAu }}"><span aria-label="No authors" style="color:var(--ui-text-dimmed)">—</span></sc-if></span></sc-if><sc-if value="{{ multi }}"><span style="color:var(--ui-text-toned);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding-right:12px">{{ a.site }}</span></sc-if>
<span><sc-if value="{{ a.busy }}"><span style="height:22px;padding:0 7px;display:inline-flex;align-items:center;gap:4px;border:1px solid var(--ui-border-accented);color:var(--ui-text-muted);font-size:12px;font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:12px;height:12px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/loader-circle.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/loader-circle.svg') center / contain no-repeat"></span>{{ a.busy }}</span></sc-if><sc-if value="{{ a.showOn }}"><span style="height:22px;padding:0 7px;display:inline-flex;align-items:center;gap:4px;background:var(--ui-bg-inverted);color:var(--ui-text-inverted);font-size:12px;font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:12px;height:12px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/globe.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/globe.svg') center / contain no-repeat"></span>Online</span></sc-if><sc-if value="{{ a.showDraft }}"><span style="height:22px;padding:0 7px;display:inline-flex;align-items:center;gap:4px;border:1px solid var(--ui-border-accented);color:var(--ui-text-toned);font-size:12px;font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:12px;height:12px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/pencil-line.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/pencil-line.svg') center / contain no-repeat"></span>Draft</span></sc-if><sc-if value="{{ a.seoSug }}"><span title="{{ a.seoTitle }}" style="margin-left:6px;height:22px;padding:0 6px;display:inline-flex;align-items:center;gap:4px;background:var(--rc-warning-soft);color:var(--ui-warning);font-size:12px;font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:12px;height:12px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/lightbulb.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/lightbulb.svg') center / contain no-repeat"></span>{{ a.seoN }}</span></sc-if><sc-if value="{{ a.seoHid }}"><span title="Hidden from search engines" style="margin-left:6px;height:22px;padding:0 6px;display:inline-flex;align-items:center;gap:4px;white-space:nowrap;flex-shrink:0;border:1px solid var(--ui-border-accented);color:var(--ui-text-toned);font-size:12px;font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:12px;height:12px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/eye-off.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/eye-off.svg') center / contain no-repeat"></span>Hidden</span></sc-if></span>
<span style="color:var(--ui-text-muted)">{{ a.c }}</span><span style="color:var(--ui-text-muted)">{{ a.u }}</span>
<span data-act="menu-{{ a.id }}" style="width:32px;height:32px;display:flex;align-items:center;justify-content:center;color:var(--ui-text-muted);background:{{ a.menuBg }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/ellipsis.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/ellipsis.svg') center / contain no-repeat"></span></span>
<sc-if value="{{ a.menu }}"><div role="menu" style="position:absolute;right:12px;top:40px;z-index:2;width:220px;background:var(--ui-bg);border:1px solid var(--ui-border);box-shadow:var(--rc-shadow-pop);padding:4px"><div data-act="open-{{ a.id }}" style="height:32px;padding:0 8px;display:flex;align-items:center;gap:8px;background:var(--ui-bg-elevated);color:var(--ui-text-highlighted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/pencil.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/pencil.svg') center / contain no-repeat"></span>Edit</div><div data-act="pub-{{ a.id }}" style="height:32px;padding:0 8px;display:flex;align-items:center;gap:8px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ a.menuIcon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ a.menuIcon }}.svg') center / contain no-repeat"></span>{{ a.menuPub }}</div><div data-act="copyurl" style="height:32px;padding:0 8px;display:flex;align-items:center;gap:8px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/link.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/link.svg') center / contain no-repeat"></span>Copy API URL</div><div style="height:1px;background:var(--ui-border);margin:4px 0"></div><div data-act="del-{{ a.id }}" style="height:32px;padding:0 8px;display:flex;align-items:center;gap:8px;color:var(--ui-error)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/trash-2.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/trash-2.svg') center / contain no-repeat"></span>Delete…</div></div></sc-if>
</sc-if>
</div>
</sc-for>
</div>
</sc-if>
<sc-if value="{{ pagerD }}"><div style="display:flex;align-items:center;gap:16px;padding:12px 0 0"><div style="display:contents"><span aria-live="polite" style="flex:1;color:var(--ui-text-muted)">{{ pager.summary }}</span><div style="position:relative"><span data-act="pg-size" role="combobox" aria-label="Rows per page" aria-expanded="{{ pager.sizeOpen }}" style="height:{{ pgBtn }};padding:0 8px 0 12px;display:flex;align-items:center;gap:8px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:var(--ui-text-highlighted);outline:{{ pager.sizeRing }};outline-offset:2px">{{ pager.size }} per page<span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat"></span></span><sc-if value="{{ pager.sizeOpen }}"><div role="listbox" style="position:absolute;left:0;bottom:calc(100% + 4px);z-index:40;width:160px;background:var(--ui-bg);border:1px solid var(--ui-border);box-shadow:var(--rc-shadow-pop);padding:4px"><sc-for list="{{ pager.sizes }}" as="o" hint-placeholder-count="3"><div role="option" aria-selected="{{ o.on }}" style="height:{{ pgBtn }};padding:0 8px;display:flex;align-items:center;gap:8px;background:{{ o.bg }};color:{{ o.fg }};font-weight:{{ o.fw }}">{{ o.t }}</div></sc-for></div></sc-if></div></div>
<sc-if value="{{ pager.ctl }}"><nav aria-label="Pagination" style="display:flex;align-items:center;gap:4px;justify-content:flex-end">
<span data-act="pg-first" aria-label="First page" style="width:{{ pgBtn }};height:{{ pgBtn }};display:flex;align-items:center;justify-content:center;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:var(--ui-text-highlighted);opacity:{{ pager.backOp }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevrons-left.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevrons-left.svg') center / contain no-repeat"></span></span>
<span data-act="pg-prev" aria-label="Previous page" style="width:{{ pgBtn }};height:{{ pgBtn }};display:flex;align-items:center;justify-content:center;gap:6px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:var(--ui-text-highlighted);opacity:{{ pager.backOp }};outline:{{ pager.prevRing }};outline-offset:2px;font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevron-left.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevron-left.svg') center / contain no-repeat"></span></span>
<sc-for list="{{ pager.nums }}" as="n" hint-placeholder-count="5"><span data-act="pg-{{ n.t }}" aria-current="{{ n.cur }}" style="min-width:{{ pgBtn }};height:{{ pgBtn }};padding:0 6px;display:flex;align-items:center;justify-content:center;border:1px solid {{ n.bd }};background:{{ n.bg }};color:{{ n.fg }};font-weight:{{ n.fw }};outline:{{ n.ring }};outline-offset:2px">{{ n.t }}</span></sc-for>
<span data-act="pg-next" aria-label="Next page" style="width:{{ pgBtn }};height:{{ pgBtn }};display:flex;align-items:center;justify-content:center;gap:6px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:var(--ui-text-highlighted);opacity:{{ pager.fwdOp }};outline:{{ pager.nextRing }};outline-offset:2px;font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevron-right.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevron-right.svg') center / contain no-repeat"></span></span>
<span data-act="pg-last" aria-label="Last page" style="width:{{ pgBtn }};height:{{ pgBtn }};display:flex;align-items:center;justify-content:center;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:var(--ui-text-highlighted);opacity:{{ pager.fwdOp }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevrons-right.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevrons-right.svg') center / contain no-repeat"></span></span>
</nav></sc-if></div></sc-if>
<sc-if value="{{ showCards }}">
<div style="background:var(--ui-bg);border:1px solid var(--ui-border);margin:0 -16px;border-left:0;border-right:0">
<sc-for list="{{ rows }}" as="a" hint-placeholder-count="6">
<div data-act="open-{{ a.id }}" style="position:relative;display:flex;align-items:center;gap:8px;padding:12px 4px 12px 16px;border-bottom:1px solid var(--ui-border);min-height:68px;opacity:{{ a.op }}">
<sc-if value="{{ a.skel }}"><div style="flex:1;display:flex;flex-direction:column;gap:8px"><span style="width:70%;height:14px;background:var(--ui-bg-accented)"></span><span style="width:40%;height:12px;background:var(--ui-bg-accented)"></span></div></sc-if>
<sc-if value="{{ a.t }}"><div style="flex:1;min-width:0;display:flex;flex-direction:column;gap:6px"><span style="font-weight:500;font-size:15px;line-height:21px;color:var(--ui-text-highlighted)">{{ a.t }}</span><div style="display:flex;align-items:center;gap:10px;font-size:12px;color:var(--ui-text-muted)"><sc-if value="{{ a.busy }}"><span style="height:20px;padding:0 6px;display:inline-flex;align-items:center;gap:4px;border:1px solid var(--ui-border-accented);font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:12px;height:12px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/loader-circle.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/loader-circle.svg') center / contain no-repeat"></span>{{ a.busy }}</span></sc-if><sc-if value="{{ a.showOn }}"><span style="height:20px;padding:0 6px;display:inline-flex;align-items:center;gap:4px;background:var(--ui-bg-inverted);color:var(--ui-text-inverted);font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:12px;height:12px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/globe.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/globe.svg') center / contain no-repeat"></span>Online</span></sc-if><sc-if value="{{ a.showDraft }}"><span style="height:20px;padding:0 6px;display:inline-flex;align-items:center;gap:4px;border:1px solid var(--ui-border-accented);color:var(--ui-text-toned);font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:12px;height:12px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/pencil-line.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/pencil-line.svg') center / contain no-repeat"></span>Draft</span></sc-if><sc-if value="{{ a.seoSug }}"><span title="{{ a.seoTitle }}" style="height:20px;padding:0 6px;display:inline-flex;align-items:center;gap:4px;background:var(--rc-warning-soft);color:var(--ui-warning);font-size:12px;font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:12px;height:12px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/lightbulb.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/lightbulb.svg') center / contain no-repeat"></span>{{ a.seoN }}</span></sc-if><sc-if value="{{ a.seoHid }}"><span title="Hidden from search engines" style="height:20px;padding:0 6px;display:inline-flex;align-items:center;gap:4px;white-space:nowrap;flex-shrink:0;border:1px solid var(--ui-border-accented);color:var(--ui-text-toned);font-size:12px;font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:12px;height:12px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/eye-off.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/eye-off.svg') center / contain no-repeat"></span>Hidden</span></sc-if><span>Updated {{ a.u }}</span></div><sc-if value="{{ authorsCol }}"><div style="display:flex;align-items:center;gap:8px;font-size:12px;color:var(--ui-text-muted);min-width:0"><sc-if value="{{ a.hasAu }}"><span style="display:flex;align-items:center"><sc-for list="{{ a.avs }}" as="v" hint-placeholder-count="2"><span style="width:22px;height:22px;margin-left:{{ v.ml }};flex-shrink:0;display:flex;align-items:center;justify-content:center;background:{{ v.bg }};color:var(--ui-text-highlighted);border:2px solid var(--ui-bg);font:600 9px/1 'IBM Plex Sans',sans-serif">{{ v.i }}</span></sc-for></span></sc-if><span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ a.auFirst }}</span><sc-if value="{{ multi }}"><span>· {{ a.site }}</span></sc-if></div></sc-if></div><span data-act="menu-{{ a.id }}" style="width:44px;height:44px;display:flex;align-items:center;justify-content:center;color:var(--ui-text-muted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:18px;height:18px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/ellipsis-vertical.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/ellipsis-vertical.svg') center / contain no-repeat"></span></span></sc-if><sc-if value="{{ a.menu }}"><div role="menu" style="position:absolute;right:8px;top:56px;z-index:2;width:220px;background:var(--ui-bg);border:1px solid var(--ui-border);box-shadow:var(--rc-shadow-pop);padding:4px"><div data-act="open-{{ a.id }}" style="height:32px;padding:0 8px;display:flex;align-items:center;gap:8px;background:var(--ui-bg-elevated);color:var(--ui-text-highlighted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/pencil.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/pencil.svg') center Line truncated
</div>
</sc-for>
</div>
</sc-if>
<sc-if value="{{ pagerM }}"><div style="display:flex;flex-direction:column;gap:12px;padding:12px 0 16px"><div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;justify-content:space-between"><span aria-live="polite" style="flex:0 1 auto;color:var(--ui-text-muted)">{{ pager.summary }}</span><div style="position:relative"><span data-act="pg-size" role="combobox" aria-label="Rows per page" aria-expanded="{{ pager.sizeOpen }}" style="height:{{ pgBtn }};padding:0 8px 0 12px;display:flex;align-items:center;gap:8px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:var(--ui-text-highlighted);outline:{{ pager.sizeRing }};outline-offset:2px">{{ pager.size }} per page<span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat"></span></span><sc-if value="{{ pager.sizeOpen }}"><div role="listbox" style="position:absolute;right:0;bottom:calc(100% + 4px);z-index:40;width:160px;background:var(--ui-bg);border:1px solid var(--ui-border);box-shadow:var(--rc-shadow-pop);padding:4px"><sc-for list="{{ pager.sizes }}" as="o" hint-placeholder-count="3"><div role="option" aria-selected="{{ o.on }}" style="height:{{ pgBtn }};padding:0 8px;display:flex;align-items:center;gap:8px;background:{{ o.bg }};color:{{ o.fg }};font-weight:{{ o.fw }}">{{ o.t }}</div></sc-for></div></sc-if></div></div>
<sc-if value="{{ pager.ctl }}"><nav aria-label="Pagination" style="display:flex;align-items:center;gap:8px;">
<span data-act="pg-prev" aria-label="Previous page" style="flex:1;height:{{ pgBtn }};display:flex;align-items:center;justify-content:center;gap:6px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:var(--ui-text-highlighted);opacity:{{ pager.backOp }};outline:{{ pager.prevRing }};outline-offset:2px;font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevron-left.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevron-left.svg') center / contain no-repeat"></span>Previous</span>
<span aria-current="page" style="flex:0 0 auto;padding:0 4px;color:var(--ui-text-highlighted);font-weight:500;white-space:nowrap">{{ pager.of }}</span>
<span data-act="pg-next" aria-label="Next page" style="flex:1;height:{{ pgBtn }};display:flex;align-items:center;justify-content:center;gap:6px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:var(--ui-text-highlighted);opacity:{{ pager.fwdOp }};outline:{{ pager.nextRing }};outline-offset:2px;font-weight:500">Next<span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevron-right.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevron-right.svg') center / contain no-repeat"></span></span>
</nav></sc-if></div></sc-if>
</div>
<sc-if value="{{ mob }}"><sc-if value="{{ listFab }}"><div data-act="new" style="position:absolute;left:16px;right:16px;bottom:16px;height:48px;display:flex;align-items:center;justify-content:center;gap:8px;background:var(--ui-primary);color:var(--ui-bg);font-weight:500;font-size:15px;box-shadow:var(--rc-shadow-pop)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:18px;height:18px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/plus.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/plus.svg') center / contain no-repeat"></span>New article</div></sc-if></sc-if>
</sc-if>
<sc-if value="{{ isEditor }}">
<sc-if value="{{ s.ed.notfound }}">
<div style="padding:{{ pad }};display:flex;flex-direction:column;gap:14px;max-width:560px"><span style="font:500 13px 'IBM Plex Mono',monospace;color:var(--ui-text-muted)">404</span><span style="font:600 24px/32px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">This article doesn't exist</span><span style="color:var(--ui-text-toned);font-size:14px;line-height:22px">It may have been deleted, or the link is wrong.</span><span data-act="nav-articles" style="align-self:flex-start;height:{{ btnH }};padding:0 12px;display:flex;align-items:center;background:var(--ui-primary);color:var(--ui-bg);font-weight:500">All articles</span></div>
</sc-if>
<sc-if value="{{ edFound }}">
<div style="flex-shrink:0;background:var(--ui-bg);border-bottom:1px solid var(--ui-border);padding:{{ edHeadPad }};display:flex;flex-wrap:wrap;align-items:center;gap:{{ edHeadGap }}">
<sc-if value="{{ desk }}"><span data-act="back" style="width:32px;height:32px;display:flex;align-items:center;justify-content:center;color:var(--ui-text-toned)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/arrow-left.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/arrow-left.svg') center / contain no-repeat"></span></span></sc-if>
<div style="flex:1 1 {{ edTitleBasis }};min-width:0;display:flex;align-items:center;gap:10px;flex-wrap:wrap">
<sc-if value="{{ s.ed.loading }}"><span style="width:320px;max-width:70%;height:20px;background:var(--ui-bg-accented)"></span><span style="width:60px;height:22px;background:var(--ui-bg-accented)"></span></sc-if>
<sc-if value="{{ edReady }}"></sc-if><span title="{{ edTitleTip }}" style="font:600 18px/26px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted);{{ edTitleCss }}">{{ edTitle }}</span><sc-if value="{{ lg }}"><span role="button" aria-haspopup="listbox" aria-expanded="{{ lg.open }}" aria-label="{{ lg.label }}" style="height:{{ lg.h }};max-width:100%;width:max-content;padding:0 8px;display:inline-flex;align-items:center;gap:6px;border:1px solid {{ lg.bd }};background:var(--ui-bg);color:var(--ui-text-highlighted);font-size:13px;font-weight:500;white-space:nowrap;flex-shrink:0"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/languages.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/languages.svg') center / contain no-repeat"></span><span>{{ lg.name }}</span><span style="flex-shrink:0;font:400 12px 'IBM Plex Mono',monospace;color:var(--ui-text-muted)">{{ lg.code }}</span><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevrons-up-down.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevrons-up-down.svg') center / contain no-repeat"></span></span><sc-if value="{{ lg.ro }}"><span style="height:26px;padding:0 9px;display:inline-flex;align-items:center;gap:5px;background:var(--ui-bg-elevated);color:var(--ui-text-toned);font-size:13px;font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/lock.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/lock.svg') center / contain no-repeat"></span>Read-only</span></sc-if><sc-if value="{{ lg.fresh }}"><span style="height:26px;padding:0 9px;display:inline-flex;align-items:center;gap:5px;border:1px solid var(--ui-warning);background:var(--ui-bg);color:var(--ui-warning);font-size:13px;font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/refresh-cw.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/refresh-cw.svg') center / contain no-repeat"></span>{{ lg.fresh }}</span></sc-if><sc-if value="{{ edPending }}"><span style="height:26px;padding:0 9px;display:inline-flex;align-items:center;gap:5px;border:1px solid var(--ui-border-accented);color:var(--ui-text-muted);font-size:13px;font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/loader-circle.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/loader-circle.svg') center / contain no-repeat"></span>{{ s.ed.pending }}</span></sc-if><sc-if value="{{ edOnline }}"><span style="height:26px;padding:0 9px;display:inline-flex;align-items:center;gap:5px;background:var(--ui-bg-inverted);color:var(--ui-text-inverted);font-size:13px;font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/globe.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/globe.svg') center / contain no-repeat"></span>Online</span></sc-if><sc-if value="{{ edDraft }}"><span style="height:26px;padding:0 9px;display:inline-flex;align-items:center;gap:5px;border:1px solid var(--ui-border-accented);color:var(--ui-text-toned);font-size:13px;font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/pencil-line.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/pencil-line.svg') center / contain no-repeat"></span>Draft</span></sc-if></sc-if>
</div>
<sc-if value="{{ edReady }}">
<span aria-live="polite" style="display:flex;align-items:center;gap:6px;white-space:nowrap;flex-shrink:0;color:{{ save.fg }}"><span aria-hidden="true" style="display:inline-block;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ save.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ save.icon }}.svg') center / contain no-repeat"></span>{{ save.t }}<sc-if value="{{ save.retry }}"><span data-act="save" style="height:24px;padding:0 8px;display:flex;align-items:center;gap:4px;border:1px solid var(--ui-border-accented);color:var(--ui-text-highlighted);font-size:12px;font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:12px;height:12px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/rotate-cw.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/rotate-cw.svg') center / contain no-repeat"></span>Retry</span></sc-if></span>
<sc-if value="{{ mob }}"><span style="flex:1"></span><span style="font:400 12px 'IBM Plex Mono',monospace;color:{{ counter.fg }}">{{ counter.t }}</span></sc-if>
<sc-if value="{{ desk }}">
<span data-act="preview" style="display:flex;align-items:center;gap:8px;white-space:nowrap;flex-shrink:0;color:var(--ui-text-toned)"><span style="width:32px;height:18px;padding:2px;display:flex;justify-content:{{ pvJust }};background:{{ pvBg }}"><span style="width:14px;height:14px;background:var(--ui-bg)"></span></span>Preview</span>
<span data-act="save" style="height:32px;padding:0 12px;display:flex;align-items:center;gap:6px;white-space:nowrap;flex-shrink:0;border:1px solid var(--ui-border-accented);color:var(--ui-text-highlighted);font-weight:500;opacity:{{ saveOp }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/save.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/save.svg') center / contain no-repeat"></span>Save</span>
<span data-act="publish" style="height:32px;padding:0 12px;display:flex;align-items:center;gap:6px;white-space:nowrap;flex-shrink:0;font-weight:500;background:{{ pub.bg }};color:{{ pub.fg }};border:1px solid {{ pub.bd }};opacity:{{ pub.op }}"><span aria-hidden="true" style="display:inline-block;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ pub.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ pub.icon }}.svg') center / contain no-repeat"></span>{{ pub.t }}</span>
<span data-act="overflow" role="button" aria-label="Delete article" style="height:32px;padding:0 12px;display:flex;align-items:center;gap:6px;white-space:nowrap;flex-shrink:0;font-weight:500;border:1px solid var(--ui-error);color:var(--ui-error);cursor:pointer"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/trash-2.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/trash-2.svg') center / contain no-repeat"></span>{{ delText }}</span>
</sc-if>
</sc-if>
</div>
{{ edBanner }}<sc-if value="{{ showToolbar }}">
<div role="toolbar" aria-label="Formatting" style="flex-shrink:0;padding:6px {{ tbPadX }};display:flex;align-items:center;gap:2px;border-bottom:1px solid var(--ui-border);position:relative;overflow:{{ tbOverflow }};background:var(--ui-bg)">
<div style="display:flex;align-items:center;gap:2px;flex-shrink:0;transform:translateX({{ tbShift }})"><sc-for list="{{ toolbar }}" as="t" hint-placeholder-count="20"><sc-if value="{{ t.sep }}"><span style="width:1px;height:20px;background:var(--ui-border-accented);margin:0 6px;flex-shrink:0"></span></sc-if><sc-if value="{{ t.btn }}"><span data-act="tb-{{ t.icon }}" style="height:{{ tbBtn }};min-width:{{ tbBtn }};padding:0 6px;flex-shrink:0;display:flex;align-items:center;justify-content:center;gap:4px;position:relative;color:{{ t.fg }};opacity:{{ t.op }};outline:{{ t.ring }};outline-offset:1px;background:{{ t.bg }}"><span aria-hidden="true" style="display:inline-block;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ t.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ t.icon }}.svg') center / contain no-repeat"></span>{{ t.text }}<sc-if value="{{ t.tip }}"><span role="tooltip" style="position:absolute;top:100%;left:50%;transform:translateX(-50%);margin-top:6px;z-index:30;white-space:nowrap;padding:4px 8px;background:var(--ui-bg-inverted);color:var(--ui-text-inverted);font:400 12px/16px 'IBM Plex Sans',sans-serif">{{ t.tip }}</span></sc-if></span></sc-if></sc-for></div><sc-if value="{{ tbFadeL }}"><span style="position:absolute;left:0;top:0;bottom:0;width:24px;background:linear-gradient(to right,var(--ui-bg),transparent);pointer-events:none"></span></sc-if><sc-if value="{{ tbFadeR }}"><span style="position:absolute;right:0;top:0;bottom:0;width:32px;background:linear-gradient(to left,var(--ui-bg),transparent);pointer-events:none"></span></sc-if>
</div>
</sc-if>
<div style="flex:1;min-height:0;display:flex">
{{ srcPane }}<div style="flex:1;min-width:0;display:flex;flex-direction:column;background:{{ edBg }}">
<div data-act="type" style="flex:1;min-height:0;overflow:hidden;padding:{{ edPad }};font-family:'Source Serif 4',serif;color:var(--ui-text-highlighted)">
<sc-if value="{{ s.ed.loading }}"><div style="display:flex;flex-direction:column;gap:14px;max-width:672px"><span style="height:14px;background:var(--ui-bg-accented)"></span><span style="height:14px;background:var(--ui-bg-accented);width:92%"></span><span style="height:14px;background:var(--ui-bg-accented);width:80%"></span><span style="height:22px;background:var(--ui-bg-accented);width:45%;margin-top:18px"></span><span style="height:14px;background:var(--ui-bg-accented)"></span><span style="height:14px;background:var(--ui-bg-accented);width:70%"></span></div></sc-if>
<sc-if value="{{ edReady }}">
<div style="max-width:672px;margin:{{ edMargin }}">
<sc-if value="{{ s.ed.preview }}"><div style="font-size:{{ h1F }};line-height:1.25;font-weight:600;margin:0 0 20px">{{ edTitle }}</div></sc-if>
<sc-if value="{{ sampleBody }}"><p style="margin:0 0 20px;font-size:{{ pF }};line-height:1.66">Every bathroom is different, but the way we price them isn't. We start with a visit, measure everything, and <strong>write down what stays</strong>.</p><sc-if value="{{ lk.on }}"><p style="margin:0 0 20px;font-size:{{ pF }};line-height:1.66">We also check the <span style="position:relative"><span data-act="link" style="{{ lk.style }}">timber windows</span><sc-if value="{{ lk.pop }}"><span role="dialog" aria-label="Link" style="position:absolute;left:0;top:100%;margin-top:8px;z-index:20;width:288px;background:var(--ui-bg);border:1px solid var(--ui-border);box-shadow:var(--rc-shadow-pop);font:400 13px/20px 'IBM Plex Sans',sans-serif;color:var(--ui-text);display:flex;flex-direction:column;text-align:left"><span style="display:flex;align-items:center;gap:8px;padding:8px 12px;border-bottom:1px solid var(--ui-border);font:400 12px/18px 'IBM Plex Mono',monospace;color:var(--ui-text-toned)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/link.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/link.svg') center / contain no-repeat"></span><span style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">/articles/winter-checklist</span></span><span style="display:flex;padding:4px;gap:2px"><span style="height:32px;padding:0 10px;display:flex;align-items:center;gap:6px;color:var(--ui-text-highlighted);background:{{ lk.popEdit }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/pencil.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/pencil.svg') center / contain no-repeat"></span>Edit</span><span style="height:32px;padding:0 10px;display:flex;align-items:center;gap:6px;color:var(--ui-text-highlighted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/external-link.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/external-link.svg') center / contain no-repeat"></span>Open</span><span style="height:32px;padding:0 10px;display:flex;align-items:center;gap:6px;color:var(--ui-error)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/unlink.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/unlink.svg') center / contain no-repeat"></span>Remove</span></span></span></sc-if></span> before any tiling starts.</p></sc-if>
<div style="font-size:{{ h2F }};line-height:1.3;font-weight:600;margin:32px 0 12px">What goes into the price</div>
<ul style="margin:0 0 20px;padding-left:28px;list-style:square;font-size:{{ pF }};line-height:1.66"><li>Stripping out and making good</li><li>Plumbing moves, if any</li><li>Tiles, fittings and a day for silicone</li></ul>
<sc-if value="{{ s.ed.image }}"><div style="margin:28px 0;aspect-ratio:16/9;background:var(--ui-bg-elevated);border:1px dashed var(--ui-border-accented);display:flex;align-items:center;justify-content:center;color:var(--ui-text-muted);font:400 13px 'IBM Plex Sans',sans-serif">/static/uploadedImages/3f9c…e1.jpg</div></sc-if>
<p style="margin:0 0 20px;font-size:{{ pF }};line-height:1.66">We don't add a margin on materials you choose yourself.<sc-if value="{{ caret }}"><span style="display:inline-block;width:2px;height:1.1em;background:var(--ui-text-highlighted);vertical-align:-3px;margin-left:1px"></span></sc-if></p></sc-if><sc-if value="{{ edBody }}"><p style="margin:0 0 20px;font-size:{{ pF }};line-height:1.66">{{ edBody }}<sc-if value="{{ caret }}"><span style="display:inline-block;width:2px;height:1.1em;background:var(--ui-text-highlighted);vertical-align:-3px;margin-left:1px"></span></sc-if></p><sc-if value="{{ s.ed.image }}"><div style="margin:28px 0;aspect-ratio:16/9;background:var(--ui-bg-elevated);border:1px dashed var(--ui-border-accented);display:flex;align-items:center;justify-content:center;color:var(--ui-text-muted);font:400 13px 'IBM Plex Sans',sans-serif">/static/uploadedImages/3f9c…e1.jpg</div></sc-if></sc-if>
</div>
</sc-if>
</div>
<sc-if value="{{ desk }}"><sc-if value="{{ showToolbar }}"><div style="flex-shrink:0;padding:8px 24px;border-top:1px solid var(--ui-border);background:var(--ui-bg);display:flex;align-items:center;gap:12px"><span style="flex:1;color:{{ counter.fg }};font-size:12px">{{ counter.msg }}</span><span style="display:flex;align-items:center;gap:6px;font:400 12px 'IBM Plex Mono',monospace;color:{{ counter.fg }}"><sc-if value="{{ counter.icon }}"><span aria-hidden="true" style="display:inline-block;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ counter.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ counter.icon }}.svg') center / contain no-repeat"></span></sc-if>{{ counter.t }}</span></div></sc-if></sc-if>
</div>
<sc-if value="{{ desk }}">
<div style="width:280px;flex-shrink:0;border-left:1px solid var(--ui-border);background:var(--ui-bg);padding:20px;display:flex;flex-direction:column;gap:16px">
<span style="font-weight:600;font-size:14px;color:var(--ui-text-highlighted)">Details</span>
<sc-if value="{{ s.ed.loading }}"><div style="display:flex;flex-direction:column;gap:12px;padding-bottom:16px;border-bottom:1px solid var(--ui-border)"><div style="display:flex;align-items:center;gap:8px;min-height:24px;color:var(--ui-text-highlighted)"><span style="display:flex;transform:rotate(-90deg)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat"></span></span><span style="font-weight:600;font-size:14px">Site and authors</span></div><span style="margin-left:22px;width:40%;height:12px;background:var(--ui-bg-accented)"></span></div><div style="display:flex;flex-direction:column;gap:10px"><span style="height:12px;background:var(--ui-bg-accented)"></span><span style="height:12px;background:var(--ui-bg-accented)"></span><span style="height:12px;width:60%;background:var(--ui-bg-accented)"></span></div></sc-if>
<sc-if value="{{ edReady }}">
<div style="display:flex;flex-direction:column;gap:12px;padding-bottom:16px;border-bottom:1px solid var(--ui-border)"><div data-act="meta-toggle" role="button" tabindex="0" aria-expanded="{{ metaOpen }}" aria-controls="meta-body" style="display:flex;align-items:center;gap:8px;min-height:24px;color:var(--ui-text-highlighted)"><span style="display:flex;transform:rotate({{ metaRot }})"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat"></span></span><span style="font-weight:600;font-size:14px">Site and authors</span></div><sc-if value="{{ metaOpen }}"><div id="meta-body" style="display:flex;flex-direction:column;gap:16px">{{ edMeta }}</div></sc-if><sc-if value="{{ metaClosed }}"><span style="padding-left:22px;color:var(--ui-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ metaSummary }}</span></sc-if></div><div style="display:flex;flex-direction:column;gap:12px;padding-bottom:16px;border-bottom:1px solid var(--ui-border)"><div data-act="seo-toggle" role="button" tabindex="0" aria-expanded="{{ seoOpen }}" aria-controls="seo-body" style="display:flex;align-items:center;gap:8px;min-height:24px;color:var(--ui-text-highlighted)"><span style="display:flex;transform:rotate({{ seoRot }})"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat"></span></span><span style="font-weight:600;font-size:14px">Search and sharing</span></div><sc-if value="{{ seoOpen }}"><div id="seo-body" style="display:flex;flex-direction:column;gap:12px">{{ edSeo }}</div></sc-if><sc-if value="{{ seoClosed }}"><span style="padding-left:22px;color:{{ seoFg }};overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ seoSummary }}</span></sc-if></div>
<div style="display:grid;grid-template-columns:auto 1fr;gap:8px 12px"><span style="color:var(--ui-text-muted)">Created</span><span>12 Mar 2026, 09:14</span><span style="color:var(--ui-text-muted)">Updated</span><span>Today, 11:02</span><span style="color:var(--ui-text-muted)">ID</span><span style="font-family:'IBM Plex Mono',monospace">12</span><span style="color:var(--ui-text-muted)">Images</span><span>{{ imgCount }}</span></div>
<div style="display:flex;flex-direction:column;gap:6px"><span style="color:var(--ui-text-muted)">Public API URL</span><span style="display:flex;align-items:center;gap:6px;padding:6px 8px;background:var(--ui-bg-elevated);font:400 12px 'IBM Plex Mono',monospace"><span style="flex:1">/v1/article/12</span><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/copy.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/copy.svg') center / contain no-repeat"></span></span><span style="color:var(--ui-text-muted);font-size:12px;line-height:18px">{{ apiNote }}</span></div>
<div style="display:flex;flex-direction:column;gap:4px;padding-top:12px;border-top:1px solid var(--ui-border)"><span style="color:var(--ui-text-muted)">Title</span><span style="font-size:12px;line-height:18px;color:var(--ui-text-toned)">{{ edTitleNote }}</span></div>
</sc-if>
</div>
</sc-if>
</div>
<sc-if value="{{ mob }}"><sc-if value="{{ edReady }}">
<div style="flex-shrink:0;padding:12px 16px;border-top:1px solid var(--ui-border);background:var(--ui-bg);display:flex;flex-direction:column;gap:8px">
<sc-if value="{{ counter.msg }}"><span style="color:{{ counter.fg }};font-size:13px;line-height:19px">{{ counter.msg }}</span></sc-if>
<div style="display:flex;gap:8px"><span data-act="details" style="width:44px;height:44px;display:flex;align-items:center;justify-content:center;border:1px solid var(--ui-border-accented);color:var(--ui-text-toned)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:18px;height:18px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/panel-right.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/panel-right.svg') center / contain no-repeat"></span></span><span data-act="save" style="flex:1;height:44px;padding:0 12px;white-space:nowrap;display:flex;align-items:center;justify-content:center;gap:6px;border:1px solid var(--ui-border-accented);color:var(--ui-text-highlighted);font-weight:500;font-size:15px;opacity:{{ saveOp }}">Save</span><span data-act="publish" style="flex:1 0 auto;padding:0 16px;white-space:nowrap;height:44px;display:flex;align-items:center;justify-content:center;gap:6px;font-weight:500;font-size:15px;background:{{ pub.bg }};color:{{ pub.fg }};border:1px solid {{ pub.bd }};opacity:{{ pub.op }}">{{ pub.t }}</span></div>
</div>
</sc-if></sc-if>
</sc-if>
</sc-if>
<sc-if value="{{ isInteg }}"><div style="position:relative;flex:1;min-height:0;overflow:hidden"><dc-import name="IntegrationPage" s="{{ s }}" mobile="{{ mob }}" style="display:flex;flex-direction:column;flex:1;min-height:0;overflow:hidden" hint-size="100%,100%"></dc-import></div></sc-if>
<sc-if value="{{ isKeys }}"><div style="display:flex;flex-direction:column;flex:1;min-height:0;overflow:hidden"><dc-import name="ApiKeysPage" s="{{ s }}" mobile="{{ mob }}" style="display:flex;flex-direction:column;flex:1;min-height:0;overflow:hidden" hint-size="100%,100%"></dc-import></div></sc-if>
<sc-if value="{{ isUsers }}">
<div style="padding:{{ pad }};display:flex;flex-direction:column;gap:16px;flex:1;min-height:0">
<div style="display:flex;align-items:flex-end;gap:16px;flex-wrap:wrap"><div style="flex:1;min-width:200px;display:flex;flex-direction:column;gap:2px"><span style="font:600 20px/28px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">Users</span><span style="color:var(--ui-text-muted)">Everyone who can sign in and work on your articles.</span></div><sc-if value="{{ desk }}"><span data-act="invite" style="height:32px;padding:0 12px;display:flex;align-items:center;gap:6px;background:var(--ui-primary);color:var(--ui-bg);font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/user-plus.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/user-plus.svg') center / contain no-repeat"></span>Invite user</span></sc-if></div>
<sc-if value="{{ usError }}"><div role="alert" style="display:flex;gap:10px;padding:12px 14px;background:var(--rc-error-soft);color:var(--ui-error)"><span style="display:flex;padding-top:2px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/circle-x.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/circle-x.svg') center / contain no-repeat"></span></span><div style="flex:1;display:flex;flex-direction:column;gap:8px"><div style="display:flex;flex-direction:column;gap:2px"><span style="font-weight:600">Couldn't load users</span><span style="color:var(--ui-text-toned)">Check your connection and try again.</span></div><span style="align-self:flex-start;height:{{ btnH }};padding:0 10px;display:flex;align-items:center;gap:6px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:var(--ui-text-highlighted);font-weight:500;font-size:12px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/rotate-cw.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/rotate-cw.svg') center / contain no-repeat"></span>Try again</span></div></div></sc-if>
<sc-if value="{{ usTable }}">
<div style="background:var(--ui-bg);border:1px solid var(--ui-border)">
<div style="display:grid;grid-template-columns:minmax(0,1.3fr) minmax(0,1fr) 84px 120px 110px 150px;gap:12px;height:36px;align-items:center;padding:0 12px;background:var(--ui-bg-elevated);color:var(--ui-text-muted);font-weight:500"><span>Email</span><span>Display name</span><span>Role</span><span>Invite status</span><span style="display:flex;align-items:center;gap:4px;color:var(--ui-text-highlighted)">Created<span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:12px;height:12px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/arrow-down.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/arrow-down.svg') center / contain no-repeat"></span></span><span></span></div>
<sc-for list="{{ usRows }}" as="r" hint-placeholder-count="5">
<div style="display:grid;grid-template-columns:minmax(0,1.3fr) minmax(0,1fr) 84px 120px 110px 150px;gap:12px;height:48px;align-items:center;padding:0 12px;border-top:1px solid var(--ui-border);background:{{ r.bg }}">
<sc-if value="{{ r.skel }}"><span style="height:12px;width:60%;background:var(--ui-bg-accented)"></span><span style="height:12px;width:50%;background:var(--ui-bg-accented)"></span><span style="width:52px;height:20px;background:var(--ui-bg-accented)"></span><span style="width:76px;height:20px;background:var(--ui-bg-accented)"></span><span style="width:80px;height:12px;background:var(--ui-bg-accented)"></span></sc-if>
<sc-if value="{{ r.email }}"><span style="display:flex;align-items:center;gap:8px;min-width:0"><span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500;color:{{ r.fg }}">{{ r.email }}</span><sc-if value="{{ r.you }}"><span style="flex-shrink:0;font:500 11px 'IBM Plex Mono',monospace;color:var(--ui-text-muted)">you</span></sc-if></span><span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:{{ r.dfg }}">{{ r.dn }}</span><span><span style="height:20px;padding:0 7px;display:inline-flex;align-items:center;background:{{ r.rl.bg }};color:{{ r.rl.fg }};border:1px solid {{ r.rl.bd }};font:500 11px 'IBM Plex Mono',monospace">{{ r.rl.t }}</span></span><span><span style="height:22px;padding:0 7px;display:inline-flex;align-items:center;gap:4px;background:{{ r.b.bg }};color:{{ r.b.fg }};border:1px solid {{ r.b.bd }};font-size:12px;font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:12px;height:12px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ r.b.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ r.b.icon }}.svg') center / contain no-repeat"></span>{{ r.b.t }}</span></span><span style="color:var(--ui-text-muted)">{{ r.c }}</span><span style="display:flex;justify-content:flex-end"><sc-if value="{{ r.act }}"><span data-act="{{ r.act.key }}" style="height:28px;padding:0 9px;white-space:nowrap;flex-shrink:0;display:inline-flex;align-items:center;gap:6px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:{{ r.act.fg }};font-weight:500;font-size:12px;opacity:{{ r.act.op }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ r.act.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ r.act.icon }}.svg') center / contain no-repeat"></span>{{ r.act.t }}</span></sc-if></span></sc-if>
</div>
</sc-for>
</div>
</sc-if>
<sc-if value="{{ usCards }}">
<div style="background:var(--ui-bg);border:1px solid var(--ui-border);margin:0 -16px;border-left:0;border-right:0">
<sc-for list="{{ usRows }}" as="r" hint-placeholder-count="5">
<div style="display:flex;flex-direction:column;justify-content:center;gap:6px;padding:12px 16px;border-bottom:1px solid var(--ui-border);min-height:68px;background:{{ r.bg }}">
<sc-if value="{{ r.skel }}"><span style="width:55%;height:14px;background:var(--ui-bg-accented)"></span><span style="width:40%;height:12px;background:var(--ui-bg-accented)"></span></sc-if>
<sc-if value="{{ r.email }}"><span style="display:flex;align-items:center;gap:8px;min-width:0"><span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:15px;line-height:21px;font-weight:500;color:{{ r.fg }}">{{ r.email }}</span><sc-if value="{{ r.you }}"><span style="flex-shrink:0;font:500 11px 'IBM Plex Mono',monospace;color:var(--ui-text-muted)">you</span></sc-if></span><sc-if value="{{ r.display2 }}"><span style="color:var(--ui-text-toned)">{{ r.dn }}</span></sc-if><div style="display:flex;align-items:center;gap:8px;font-size:12px;color:var(--ui-text-muted)"><span style="height:20px;padding:0 7px;display:inline-flex;align-items:center;background:{{ r.rl.bg }};color:{{ r.rl.fg }};border:1px solid {{ r.rl.bd }};font:500 11px 'IBM Plex Mono',monospace">{{ r.rl.t }}</span><span style="height:20px;padding:0 6px;display:inline-flex;align-items:center;gap:4px;background:{{ r.b.bg }};color:{{ r.b.fg }};border:1px solid {{ r.b.bd }};font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:12px;height:12px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ r.b.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ r.b.icon }}.svg') center / contain no-repeat"></span>{{ r.b.t }}</span><span>Created {{ r.c }}</span></div><sc-if value="{{ r.act }}"><div style="display:flex;padding-top:4px"><span data-act="{{ r.act.key }}" style="height:44px;padding:0 14px;white-space:nowrap;flex-shrink:0;display:inline-flex;align-items:center;gap:8px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:{{ r.act.fg }};font-weight:500;font-size:13px;opacity:{{ r.act.op }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ r.act.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ r.act.icon }}.svg') center / contain no-repeat"></span>{{ r.act.t }}</span></div></sc-if></sc-if>
</div>
</sc-for>
</div>
</sc-if>
<sc-if value="{{ usOnly }}"><span style="color:var(--ui-text-muted);text-wrap:pretty">Invite someone so they can sign in with their own account instead of sharing yours.</span></sc-if>
</div>
<sc-if value="{{ mob }}"><sc-if value="{{ listFab }}"><div data-act="invite" style="position:absolute;left:16px;right:16px;bottom:16px;height:48px;display:flex;align-items:center;justify-content:center;gap:8px;background:var(--ui-primary);color:var(--ui-bg);font-weight:500;font-size:15px;box-shadow:var(--rc-shadow-pop)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:18px;height:18px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/user-plus.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/user-plus.svg') center / contain no-repeat"></span>Invite user</div></sc-if></sc-if>
</sc-if>
<sc-if value="{{ isSettings }}">
<div style="padding:{{ pad }};display:flex;flex-direction:column;gap:16px;max-width:720px;flex-shrink:0;margin-top:{{ setScroll }}">
<div style="display:flex;flex-direction:column;gap:2px"><span style="font:600 20px/28px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">Settings</span><span style="color:var(--ui-text-muted)">Workspace information.</span></div>
<div style="background:var(--ui-bg);border:1px solid var(--ui-border);padding:{{ cardPad }};display:flex;align-items:center;justify-content:space-between;gap:16px"><span style="font:600 15px/24px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">Version</span><span style="font:400 14px/22px 'IBM Plex Mono',monospace;color:var(--ui-text-toned)">{{ s.upd.v }}</span></div>
<div role="region" aria-labelledby="updates-h" style="background:var(--ui-bg);border:1px solid var(--ui-border);padding:{{ cardPad }};display:flex;flex-direction:column;gap:16px">
<span id="updates-h" tabindex="-1" style="align-self:flex-start;font:600 15px/24px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted);outline:{{ s.upd.focus }};outline-offset:4px">Updates</span>
<div aria-live="polite" style="display:flex;gap:12px;padding:14px;background:{{ s.upd.bg }}"><span style="display:flex;padding-top:2px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:18px;height:18px;background-color:{{ s.upd.fg }};-webkit-mask:url('https://api.iconify.design/lucide/{{ s.upd.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ s.upd.icon }}.svg') center / contain no-repeat"></span></span><div style="flex:1;min-width:0;display:flex;flex-direction:column;gap:4px"><div style="display:flex;align-items:center;flex-wrap:wrap;gap:8px"><span style="font:600 15px/24px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">{{ s.upd.title }}</span><sc-if value="{{ s.upd.pendingTag }}"><span style="height:20px;padding:0 6px;display:inline-flex;align-items:center;background:var(--ui-bg);color:var(--ui-warning);border:1px solid var(--ui-warning);font:500 11px 'IBM Plex Mono',monospace">Pending</span></sc-if></div><span style="color:var(--ui-text-toned);text-wrap:pretty">{{ s.upd.text }}</span><sc-if value="{{ s.upd.bar }}"><div style="display:flex;flex-direction:column;gap:6px;padding-top:8px"><div role="progressbar" aria-label="{{ s.upd.barLabel }}" aria-valuemin="0" aria-valuemax="100" aria-valuenow="{{ s.upd.pct }}" style="height:6px;background:var(--ui-bg-accented)"><div style="height:6px;width:{{ s.upd.barW }};margin-left:{{ s.upd.barL }};background:var(--ui-primary)"></div></div><span style="font:400 12px/16px 'IBM Plex Mono',monospace;color:var(--ui-text-muted)">{{ s.upd.barText }}</span></div></sc-if><sc-if value="{{ s.upd.meta }}"><span style="font-size:12px;color:var(--ui-text-muted)">{{ s.upd.meta }}</span></sc-if><sc-if value="{{ s.upd.inBtn }}"><div style="display:flex;padding-top:8px"><span role="button" style="height:{{ btnH }};padding:0 14px;display:flex;align-items:center;justify-content:center;gap:6px;background:{{ s.upd.inBtn.bg }};color:{{ s.upd.inBtn.fg }};border:1px solid {{ s.upd.inBtn.bd }};font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ s.upd.inBtn.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ s.upd.inBtn.icon }}.svg') center / contain no-repeat"></span>{{ s.upd.inBtn.t }}</span></div></sc-if></div></div>
<sc-if value="{{ s.upd.notes }}"><div style="display:flex;flex-direction:column;gap:10px"><span role="button" aria-expanded="{{ s.upd.notes.open }}" style="align-self:flex-start;display:flex;align-items:center;gap:6px;min-height:{{ btnH }};font-weight:500;color:var(--ui-text-highlighted)"><span aria-hidden="true" style="display:inline-block;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ s.upd.notes.chev }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ s.upd.notes.chev }}.svg') center / contain no-repeat"></span>What's new in {{ s.upd.notes.ver }}</span><sc-if value="{{ s.upd.notes.open }}"><div style="display:flex;flex-direction:column;gap:8px;padding:0 0 0 20px"><span style="font-size:12px;color:var(--ui-text-muted)">{{ s.upd.notes.date }}</span><sc-for list="{{ s.upd.notes.items }}" as="it" hint-placeholder-count="3"><div style="display:flex;gap:8px"><span aria-hidden="true" style="flex-shrink:0;width:4px;height:4px;margin-top:8px;background:var(--ui-text-muted)"></span><span style="color:var(--ui-text-toned);text-wrap:pretty">{{ it.t }}</span></div></sc-for><span style="align-self:flex-start;color:var(--ui-text-highlighted);text-decoration:underline;text-underline-offset:3px;font-weight:500">Read the full release notes</span></div></sc-if></div></sc-if>
<sc-if value="{{ s.upd.cmd }}"><div style="display:flex;flex-direction:column;gap:8px"><span style="font-weight:500;color:var(--ui-text-highlighted)">For whoever manages the server</span><div style="display:flex;flex-direction:{{ upCmdDir }};align-items:{{ upBtnAlign }};gap:8px"><code style="flex:1;min-width:0;padding:8px 12px;background:var(--ui-bg-elevated);color:var(--ui-text-highlighted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:22px">{{ s.upd.cmd.line }}</code><span role="button" style="height:{{ btnH }};padding:0 12px;display:flex;align-items:center;justify-content:center;gap:6px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:{{ s.upd.cmd.fg }};font-weight:500"><span aria-hidden="true" style="display:inline-block;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ s.upd.cmd.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ s.upd.cmd.icon }}.svg') center / contain no-repeat"></span>{{ s.upd.cmd.t }}</span></div><span style="font-size:12px;color:var(--ui-text-muted)">{{ s.upd.cmd.note }}</span></div></sc-if>
<sc-if value="{{ s.upd.btns }}"><div style="display:flex;flex-direction:{{ upBtnDir }};align-items:{{ upBtnAlign }};gap:8px"><sc-for list="{{ s.upd.btns }}" as="b" hint-placeholder-count="1"><span role="button" style="height:{{ btnH }};padding:0 14px;display:flex;align-items:center;justify-content:center;gap:6px;background:{{ b.bg }};color:{{ b.fg }};border:1px solid {{ b.bd }};opacity:{{ b.op }};font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ b.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ b.icon }}.svg') center / contain no-repeat"></span>{{ b.t }}</span></sc-for></div></sc-if>
<sc-if value="{{ s.upd.hint }}"><span style="font-size:12px;color:var(--ui-text-muted);text-wrap:pretty">{{ s.upd.hint }}</span></sc-if>
<sc-if value="{{ s.upd.link }}"><span style="align-self:flex-start;display:flex;align-items:center;gap:6px;color:var(--ui-text-highlighted);text-decoration:underline;text-underline-offset:3px;font-weight:500">{{ s.upd.link }}<span aria-hidden="true" style="display:inline-block;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/external-link.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/external-link.svg') center / contain no-repeat"></span></span></sc-if>
</div>
<div style="background:var(--ui-bg);border:1px solid var(--ui-border);padding:{{ cardPad }};display:flex;flex-direction:column;gap:12px"><div style="display:flex;flex-direction:column;gap:2px"><span style="font:600 15px/24px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">Media</span><span style="color:var(--ui-text-muted);text-wrap:pretty">Image sizes, cache and cleanup for your media library.</span></div><div style="display:flex;flex-direction:column"><div style="display:flex;align-items:center;gap:12px;min-height:44px;padding:8px 0;border-top:1px solid var(--ui-border);color:var(--ui-text-highlighted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/layers.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/layers.svg') center / contain no-repeat"></span><div style="flex:1;min-width:0;display:flex;flex-direction:column"><span style="font-weight:500">Manage image variants</span><span style="color:var(--ui-text-muted)">Named sizes requested with ?v=</span></div><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevron-right.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevron-right.svg') center / contain no-repeat"></span></div><div style="display:flex;align-items:center;gap:12px;min-height:44px;padding:8px 0;border-top:1px solid var(--ui-border);color:var(--ui-text-highlighted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/database.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/database.svg') center / contain no-repeat"></span><div style="flex:1;min-width:0;display:flex;flex-direction:column"><span style="font-weight:500">Cache usage</span><span style="color:var(--ui-text-muted)">Generated images and disk space</span></div><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevron-right.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevron-right.svg') center / contain no-repeat"></span></div></div></div>
<sc-if value="{{ showSites }}"><div style="background:var(--ui-bg);border:1px solid var(--ui-border);padding:{{ cardPad }};display:flex;flex-direction:column;gap:12px"><div style="display:flex;flex-direction:column;gap:2px"><span style="font:600 15px/24px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">Publishing</span><span style="color:var(--ui-text-muted);text-wrap:pretty">Where your content is shown.</span></div><div style="display:flex;flex-direction:column"><div style="display:flex;align-items:center;gap:12px;min-height:44px;padding:8px 0;border-top:1px solid var(--ui-border);color:var(--ui-text-highlighted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/globe.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/globe.svg') center / contain no-repeat"></span><div style="flex:1;min-width:0;display:flex;flex-direction:column"><span style="font-weight:500">Manage sites</span><span style="color:var(--ui-text-muted)">Base URLs, default image and robots policy</span></div><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevron-right.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevron-right.svg') center / contain no-repeat"></span></div></div></div></sc-if>
<div role="region" aria-labelledby="cleanup-h" style="background:var(--ui-bg);border:1px solid var(--ui-border);padding:{{ cardPad }};display:flex;flex-direction:column;gap:16px"><sc-if value="{{ cl.loading }}"><div aria-busy="true" style="display:flex;flex-direction:column;gap:16px"><span style="display:block;width:140px;height:16px;background:var(--ui-bg-accented)"></span><span style="display:block;width:90%;height:13px;background:var(--ui-bg-accented)"></span><span style="display:block;width:55%;height:13px;background:var(--ui-bg-accented)"></span><div style="display:flex;justify-content:space-between;align-items:center"><div style="display:flex;flex-direction:column;gap:6px"><span style="display:block;width:120px;height:13px;background:var(--ui-bg-accented)"></span><span style="display:block;width:180px;height:11px;background:var(--ui-bg-accented)"></span></div><span style="display:block;width:36px;height:20px;background:var(--ui-bg-accented)"></span></div><span style="display:block;width:60%;height:13px;background:var(--ui-bg-accented)"></span><div style="height:1px;background:var(--ui-border)"></div><div style="display:flex;justify-content:space-between;align-items:center"><div style="display:flex;flex-direction:column;gap:6px"><span style="display:block;width:100px;height:13px;background:var(--ui-bg-accented)"></span><span style="display:block;width:220px;height:11px;background:var(--ui-bg-accented)"></span></div><span style="display:block;width:140px;height:32px;background:var(--ui-bg-accented)"></span></div></div></sc-if><sc-if value="{{ cl.ready }}"><div style="display:flex;align-items:flex-start;gap:12px"><div style="flex:1;display:flex;flex-direction:column;gap:2px"><span id="cleanup-h" tabindex="-1" style="align-self:flex-start;font:600 15px/24px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">Media cleanup</span><span style="color:var(--ui-text-muted);text-wrap:pretty">Merge identical images so your library stays tidy. Articles that use a copy are switched to the most used one.</span></div><div style="height:24px;display:flex;align-items:center;flex-shrink:0"><sc-if value="{{ cl.saving }}"><span style="display:flex;align-items:center;gap:6px;font-size:12px;color:var(--ui-text-muted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/loader-circle.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/loader-circle.svg') center / contain no-repeat;"></span>Saving…</span></sc-if><sc-if value="{{ cl.saved }}"><span role="status" style="display:flex;align-items:center;gap:6px;font-size:12px;color:var(--ui-success)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/check.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/check.svg') center / contain no-repeat;"></span>Saved</span></sc-if></div></div><sc-if value="{{ cl.ro }}"><div style="display:flex;gap:10px;padding:12px 14px;background:var(--rc-info-soft);color:var(--ui-info)"><span style="display:flex;padding-top:2px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/info.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/info.svg') center / contain no-repeat;"></span></span><span style="color:var(--ui-text-toned)">Only owners and admins can change this.</span></div></sc-if><div style="display:flex;flex-direction:{{ rowDir }};align-items:{{ rowAlign }};justify-content:{{ rowJust }};gap:{{ rowGap }}"><div style="display:flex;flex-direction:column;gap:2px;min-width:0"><span style="font-weight:500;color:var(--ui-text-highlighted)">Automatic purge</span><span style="font-size:12px;color:var(--ui-text-muted)">Runs in the background. Off by default.</span></div><div role="switch" aria-checked="{{ cl.on }}" style="display:flex;align-items:center;justify-content:center;width:{{ cl.swW }};height:{{ cl.swH }};flex-shrink:0;opacity:{{ cl.swOp }}"><div style="position:relative;width:36px;height:20px;border:1px solid {{ cl.swBd }};background:{{ cl.swBg }};outline:{{ cl.swRing }};outline-offset:2px"><div style="position:absolute;top:1px;left:{{ cl.knobL }};width:16px;height:16px;background:var(--ui-bg);border:1px solid {{ cl.knobBd }};box-sizing:border-box"></div></div></div></div><sc-if value="{{ cl.on }}"><div style="display:flex;flex-direction:{{ rowDir }};align-items:{{ rowAlign }};justify-content:{{ rowJust }};gap:{{ rowGap }}"><div style="display:flex;flex-direction:column;gap:2px;min-width:0"><span style="font-weight:500;color:var(--ui-text-highlighted)">Every</span><span style="font-size:12px;color:var(--ui-text-muted)">Between 1 and 365 days.</span></div><div style="display:flex;flex-direction:column;gaLine truncated
<sc-if value="{{ rt.show }}"><div role="region" aria-labelledby="retention-h" style="background:var(--ui-bg);border:1px solid var(--ui-border);padding:{{ cardPad }};display:flex;flex-direction:column;gap:16px"><div style="display:flex;align-items:flex-start;gap:12px"><div style="flex:1;display:flex;flex-direction:column;gap:2px"><span id="retention-h" tabindex="-1" style="align-self:flex-start;font:600 15px/24px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">Retention</span><span style="color:var(--ui-text-muted);text-wrap:pretty">How long activity is kept, and how much log data to keep. Whichever limit is reached first removes the oldest events. Only owners can change it.</span></div><div style="height:24px;display:flex;align-items:center;flex-shrink:0"><sc-if value="{{ rt.saving }}"><span style="display:flex;align-items:center;gap:6px;font-size:12px;color:var(--ui-text-muted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/loader-circle.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/loader-circle.svg') center / contain no-repeat;"></span>Saving…</span></sc-if><sc-if value="{{ rt.saved }}"><span role="status" style="display:flex;align-items:center;gap:6px;font-size:12px;color:var(--ui-success)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/check.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/check.svg') center / contain no-repeat;"></span>Saved</span></sc-if></div></div><sc-if value="{{ rt.err }}"><div style="display:flex;gap:10px;padding:12px 14px;background:var(--rc-error-soft);color:var(--ui-error)"><span style="display:flex;padding-top:2px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/circle-x.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/circle-x.svg') center / contain no-repeat;"></span></span><div style="flex:1;display:flex;flex-direction:column;gap:8px"><div style="display:flex;flex-direction:column;gap:2px"><span style="font-weight:600">Couldn't save the retention period</span><span style="color:var(--ui-text-toned)">Your previous setting is still in place.</span></div><span role="button" style="height:28px;padding:0 10px;display:flex;align-items:center;justify-content:center;gap:6px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:var(--ui-text-highlighted);font-weight:500;font-size:12px;white-space:nowrap;opacity:1">Try again</span></div></div></sc-if><div style="display:flex;flex-direction:{{ rowDir }};align-items:{{ rowAlign }};justify-content:{{ rowJust }};gap:{{ rowGap }}"><div style="display:flex;flex-direction:column;gap:2px;min-width:0"><span style="font-weight:500;color:var(--ui-text-highlighted)">Keep activity for</span><span style="font-size:12px;color:var(--ui-text-muted)">{{ rt.help }}</span></div><div style="display:flex;align-items:center;width:{{ rt.selW }};min-height:{{ inH }};padding:0 12px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);font-size:{{ inF }};color:var(--ui-text-highlighted)"><span style="flex:1">{{ rt.val }}</span><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat;color:var(--ui-text-muted)"></span></div></div><div style="display:flex;align-items:center;gap:12px;color:var(--ui-text-muted);font-size:12px;font-weight:500"><span style="flex:1;height:1px;background:var(--ui-border)"></span><span>or</span><span style="flex:1;height:1px;background:var(--ui-border)"></span></div><div style="display:flex;flex-direction:{{ rowDir }};align-items:{{ rowAlign }};justify-content:{{ rowJust }};gap:{{ rowGap }}"><div style="display:flex;flex-direction:column;gap:2px;min-width:0"><span style="font-weight:500;color:var(--ui-text-highlighted)">Keep at most</span><span style="font-size:12px;color:var(--ui-text-muted)">{{ rt.sizeHelp }}</span></div><div style="display:flex;align-items:center;width:{{ rt.selW }};min-height:{{ inH }};padding:0 12px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);font-size:{{ inF }};color:var(--ui-text-highlighted)"><span style="flex:1">{{ rt.sizeVal }}</span><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat;cLine truncated
</div>
</sc-if>
<sc-if value="{{ isProfile }}">
<div style="padding:{{ pad }};display:flex;flex-direction:column;gap:16px;max-width:720px;flex-shrink:0;margin-top:{{ pfShift }}">
<div style="display:flex;flex-direction:column;gap:2px"><span style="font:600 20px/28px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">Profile</span><span style="color:var(--ui-text-muted)">Your account on this RenewCMS workspace.</span></div>
<sc-if value="{{ pfLoading }}"><div aria-busy="true" style="display:flex;flex-direction:column;gap:16px"><div style="background:var(--ui-bg);border:1px solid var(--ui-border);padding:{{ cardPad }};display:flex;flex-direction:column;gap:12px"><span style="width:120px;height:14px;background:var(--ui-bg-accented)"></span><span style="height:40px;background:var(--ui-bg-accented)"></span></div><div style="background:var(--ui-bg);border:1px solid var(--ui-border);padding:{{ cardPad }};display:flex;flex-direction:column;gap:12px"><span style="width:90px;height:14px;background:var(--ui-bg-accented)"></span><span style="height:40px;background:var(--ui-bg-accented)"></span><span style="height:40px;background:var(--ui-bg-accented)"></span></div><div style="background:var(--ui-bg);border:1px solid var(--ui-border);padding:{{ cardPad }};display:flex;flex-direction:column;gap:12px"><span style="width:100px;height:14px;background:var(--ui-bg-accented)"></span><span style="height:40px;background:var(--ui-bg-accented)"></span></div></div></sc-if>
<sc-if value="{{ pfReady }}">
<div style="background:var(--ui-bg);border:1px solid var(--ui-border)">
<div style="padding:{{ cardPad }};display:flex;flex-direction:column;gap:14px">
<div style="display:flex;flex-direction:column;gap:2px"><span style="font-weight:600;font-size:14px;color:var(--ui-text-highlighted)">Display name</span><span style="color:var(--ui-text-muted);text-wrap:pretty">Shown instead of your username across the back-office and in the emails RenewCMS sends. Leave it empty to use your username.</span></div>
<sc-if value="{{ pf.notice }}"><div role="alert" style="display:flex;gap:8px;padding:10px 12px;background:var(--rc-error-soft);color:var(--ui-error)"><span style="display:flex;padding-top:2px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/circle-x.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/circle-x.svg') center / contain no-repeat"></span></span><span style="color:var(--ui-text-toned)">{{ pf.notice }}</span></div></sc-if>
<div style="display:flex;flex-direction:column;gap:6px">
<label style="font-weight:500;color:var(--ui-text-highlighted)">Display name <span style="font-weight:400;color:var(--ui-text-muted)">(optional)</span></label>
<div style="height:{{ inH }};padding:0 10px;display:flex;align-items:center;border:1px solid {{ dn.bd }};box-shadow:{{ dn.sh }};background:{{ dn.bg }};color:{{ dn.fg }};font-size:{{ inF }};overflow:hidden;white-space:nowrap"><span style="overflow:hidden;text-overflow:ellipsis">{{ dn.shown }}</span><sc-if value="{{ dn.caret }}"><span style="flex-shrink:0;display:inline-block;width:1px;height:18px;background:var(--ui-text-highlighted);margin-left:1px"></span></sc-if></div>
<div style="display:flex;gap:12px;align-items:flex-start"><span style="flex:1;display:flex;gap:6px;align-items:center;color:var(--ui-error);font-size:12px"><sc-if value="{{ dn.err }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/circle-alert.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/circle-alert.svg') center / contain no-repeat"></span>{{ dn.err }}</sc-if></span><span style="font:400 12px 'IBM Plex Mono',monospace;color:{{ dn.countFg }}">{{ dn.count }}</span></div>
</div>
</div>
<div style="padding:12px {{ cardPadX }};border-top:1px solid var(--ui-border);background:var(--ui-bg-muted);display:flex;align-items:center;gap:12px"><span style="flex:1;min-width:0;color:{{ dn.footFg }};font-size:12px;line-height:18px">{{ dn.foot }}</span><span data-act="pf-save" style="height:{{ btnH }};padding:0 14px;display:flex;align-items:center;gap:6px;flex-shrink:0;background:var(--ui-primary);color:var(--ui-bg);font-weight:500;opacity:{{ dnSave.op }}"><sc-if value="{{ dnSave.loading }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/loader-circle.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/loader-circle.svg') center / contain no-repeat"></span></sc-if>{{ dnSave.t }}</span></div>
</div>
<div style="background:var(--ui-bg);border:1px solid var(--ui-border);padding:{{ cardPad }};display:flex;flex-direction:column;gap:14px">
<div style="display:flex;flex-direction:column;gap:2px"><span style="font-weight:600;font-size:14px;color:var(--ui-text-highlighted)">Account</span><span style="color:var(--ui-text-muted);text-wrap:pretty">Set when your account was created. You log in with your username, even after adding a display name.</span></div>
<div style="display:grid;grid-template-columns:{{ pfCols }};gap:14px"><div style="display:flex;flex-direction:column;gap:6px;min-width:0"><span style="font-weight:500;color:var(--ui-text-highlighted)">Username</span><div aria-disabled="true" style="height:{{ inH }};padding:0 10px;display:flex;align-items:center;gap:8px;border:1px solid var(--ui-border);background:var(--ui-bg-elevated);color:var(--ui-text-toned);font-size:{{ inF }};cursor:not-allowed"><span style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">marta</span><span style="display:flex;color:var(--ui-text-muted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/lock.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/lock.svg') center / contain no-repeat"></span></span></div></div><dc-import name="AccountCards" part="email" ac="{{ pfAc }}" c="{{ pfC }}" mobile="{{ mob }}" hint-size="100%,60px" style="display:block;height:auto;min-height:0;flex-shrink:0"></dc-import></div>
<sc-if value="{{ pfPanel }}"><dc-import name="AccountCards" part="panel" ac="{{ pfAc }}" c="{{ pfC }}" mobile="{{ mob }}" hint-size="100%,60px" style="display:block;height:auto;min-height:0;flex-shrink:0"></dc-import></sc-if>
</div>
<div style="background:var(--ui-bg);border:1px solid var(--ui-border);padding:{{ cardPad }};display:flex;flex-direction:column;gap:14px"><div style="display:flex;flex-direction:column;gap:2px"><span style="font-weight:600;font-size:14px;color:var(--ui-text-highlighted)">Author profile</span><span style="color:var(--ui-text-muted);text-wrap:pretty">Your public byline on articles. It's separate from your account.</span></div>
<sc-if value="{{ pAu.err }}"><div role="alert" style="display:flex;gap:10px;padding:12px 14px;background:var(--rc-error-soft);color:var(--ui-error)"><span style="display:flex;padding-top:2px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/circle-x.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/circle-x.svg') center / contain no-repeat"></span></span><div style="display:flex;flex-direction:column;gap:2px"><span style="font-weight:600">Couldn't create your author profile</span><span style="color:var(--ui-text-toned)">Try again in a moment.</span></div></div></sc-if>
<sc-if value="{{ pAu.none }}"><span style="color:var(--ui-text-toned);text-wrap:pretty">You don't have an author profile yet. Create one to be credited on your own articles.</span><div style="display:flex;flex-direction:{{ pwDir }};align-items:{{ pwAlign }};gap:12px"><span style="height:{{ btnH }};padding:0 12px;display:flex;align-items:center;justify-content:center;gap:6px;background:var(--ui-primary);color:var(--ui-bg);font-weight:500;opacity:{{ pAu.op }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ pAu.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ pAu.icon }}.svg') center / contain no-repeat"></span>{{ pAu.btn }}</span><span style="color:var(--ui-text-muted);font-size:12px">{{ pAu.uses }}</span></div></sc-if>
<sc-if value="{{ pAu.has }}"><div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap"><span style="width:40px;height:40px;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--rc-sand);color:var(--rc-on-sand);border:1px solid var(--ui-border);font:600 14px/1 'IBM Plex Sans',sans-serif">MA</span><div style="flex:1;min-width:140px;display:flex;flex-direction:column"><span style="font-weight:500;color:var(--ui-text-highlighted)">Marta Alves</span><span style="font:400 12px/18px 'IBM Plex Mono',monospace;color:var(--ui-text-muted)">{{ pAu.slug }}</span></div><span style="height:{{ btnH }};padding:0 12px;display:flex;align-items:center;justify-content:center;gap:6px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:var(--ui-text-highlighted);font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/pencil.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/pencil.svg') center / contain no-repeat"></span>Edit author</span></div>
<sc-if value="{{ pAu.slugNote }}"><div role="status" style="display:flex;gap:10px;padding:12px 14px;background:var(--rc-info-soft);color:var(--ui-info)"><span style="display:flex;padding-top:2px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/info.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/info.svg') center / contain no-repeat"></span></span><div style="display:flex;flex-direction:column;gap:2px"><span style="font-weight:600">Your slug is {{ pAu.slug }}</span><span style="color:var(--ui-text-toned)">marta-alves was already used by another author. You can change it in Edit author.</span></div></div></sc-if>
<div style="display:flex;align-items:center;gap:6px;color:var(--ui-text-muted);font-size:12px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:12px;height:12px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/link.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/link.svg') center / contain no-repeat"></span>Linked to your account. New articles start with this author.</div></sc-if></div>
<div style="background:var(--ui-bg);border:1px solid var(--ui-border);padding:{{ cardPad }};display:flex;flex-direction:column;gap:14px">
<div style="display:flex;flex-direction:column;gap:2px"><span style="font-weight:600;font-size:14px;color:var(--ui-text-highlighted)">Password</span><span style="color:var(--ui-text-muted);text-wrap:pretty">To change it, we'll email a reset link to marta@renew.example. The link works once, within 1 hour.</span></div>
<div style="display:flex;flex-direction:{{ pwDir }};align-items:{{ pwAlign }};gap:12px"><div style="flex:1;min-width:0"><div style="display:flex;flex-direction:column;gap:6px;min-width:0"><span style="font-weight:500;color:var(--ui-text-highlighted)">Password</span><div aria-disabled="true" style="height:{{ inH }};padding:0 10px;display:flex;align-items:center;gap:8px;border:1px solid var(--ui-border);background:var(--ui-bg-elevated);color:var(--ui-text-toned);font-size:{{ inF }};cursor:not-allowed"><span style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0.12em">••••••••••••</span><span style="display:flex;color:var(--ui-text-muted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/lock.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/lock.svg') center / contain no-repeat"></span></span></div></div></div><span data-act="pf-reset" style="height:{{ inH }};padding:0 14px;display:flex;align-items:center;justify-content:center;gap:6px;flex-shrink:0;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:var(--ui-text-highlighted);font-weight:500;white-space:nowrap;opacity:{{ rsBtn.op }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ rsBtn.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ rsBtn.icon }}.svg') center / contain no-repeat"></span>{{ rsBtn.t }}</span></div>
<sc-if value="{{ rsNote }}"><div role="status" style="display:flex;gap:10px;padding:12px 14px;background:{{ rsNote.bg }}"><span style="display:flex;padding-top:2px;color:{{ rsNote.fg }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ rsNote.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ rsNote.icon }}.svg') center / contain no-repeat"></span></span><div style="display:flex;flex-direction:column;gap:2px"><span style="font-weight:600;color:{{ rsNote.fg }}">{{ rsNote.title }}</span><span style="color:var(--ui-text-toned);text-wrap:pretty">{{ rsNote.text }}</span></div></div></sc-if>
</div>
<dc-import name="AccountCards" part="cards" ac="{{ pfAc }}" c="{{ pfC }}" mobile="{{ mob }}" hint-size="100%,60px" style="display:block;height:auto;min-height:0;flex-shrink:0"></dc-import>
</sc-if>
</div>
</sc-if>
<sc-if value="{{ isError }}">
<div style="padding:{{ errPad }};display:flex;flex-direction:column;gap:14px;max-width:560px"><span style="font:500 13px 'IBM Plex Mono',monospace;color:var(--ui-text-muted)">{{ s.err.code }}</span><span style="font:600 24px/32px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">{{ s.err.title }}</span><span style="color:var(--ui-text-toned);font-size:14px;line-height:22px;text-wrap:pretty">{{ s.err.text }}</span><div style="display:flex;gap:8px;flex-wrap:wrap"><span data-act="err-primary" style="height:{{ btnH }};padding:0 12px;display:flex;align-items:center;gap:6px;background:var(--ui-primary);color:var(--ui-bg);font-weight:500">{{ s.err.primary }}</span><sc-if value="{{ s.err.secondary }}"><span style="height:{{ btnH }};padding:0 12px;display:flex;align-items:center;border:1px solid var(--ui-border-accented);color:var(--ui-text-highlighted);font-weight:500">{{ s.err.secondary }}</span></sc-if></div></div>
</sc-if>
</div>
</div>
<sc-if value="{{ mobMenu }}"><div style="position:absolute;inset:0;z-index:50;background:var(--rc-overlay);display:flex;align-items:flex-end"><div role="menu" aria-label="Account" style="width:100%;background:var(--ui-bg);box-shadow:var(--rc-shadow-modal);padding:8px 0 24px"><div style="margin:0 auto 8px;width:36px;height:4px;background:var(--ui-bg-accented)"></div><div style="padding:8px 16px 14px;display:flex;align-items:center;gap:12px;border-bottom:1px solid var(--ui-border);margin-bottom:4px"><span style="width:36px;height:36px;flex-shrink:0;background:var(--rc-sand);color:var(--rc-on-sand);display:flex;align-items:center;justify-content:center;font:600 13px 'IBM Plex Sans',sans-serif">{{ ini }}</span><div style="display:flex;flex-direction:column;min-width:0"><span style="font-weight:600;font-size:15px;color:var(--ui-text-highlighted)">{{ who }}</span><span style="color:var(--ui-text-muted)">marta@renew.example</span></div></div><div data-act="nav-profile" role="menuitem" style="height:48px;padding:0 16px;display:flex;align-items:center;gap:12px;font-size:15px;background:{{ pfItemBg }};color:var(--ui-text-highlighted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:18px;height:18px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/circle-user.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/circle-user.svg') center / contain no-repeat"></span>Profile</div><div style="height:1px;background:var(--ui-border);margin:4px 0"></div><div style="padding:8px 16px 2px;font-size:11px;font-weight:500;letter-spacing:.04em;text-transform:uppercase;color:var(--ui-text-muted)">Theme</div><sc-for list="{{ themeSeg }}" as="m" hint-placeholder-count="3"><div data-act="{{ m.act }}" role="menuitemradio" aria-checked="{{ m.on }}" style="height:48px;padding:0 16px;display:flex;align-items:center;justify-content:space-between;font-size:15px;color:var(--ui-text-highlighted)">{{ m.t }}<span aria-hidden="true" style="display:inline-block;width:18px;height:18px;background-color:currentColor;opacity:{{ m.op }};-webkit-mask:url('https://api.iconify.design/lucide/check.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/check.svg') center / contain no-repeat"></span></div></sc-for><div style="height:1px;background:var(--ui-border);margin:4px 0"></div><div style="padding:8px 16px 2px;font-size:11px;font-weight:500;letter-spacing:.04em;text-transform:uppercase;color:var(--ui-text-muted)">Language</div><sc-for list="{{ langSeg }}" as="m" hint-placeholder-count="2"><div data-act="{{ m.act }}" lang="{{ m.code }}" role="menuitemradio" aria-checked="{{ m.on }}" style="height:48px;padding:0 16px;display:flex;align-items:center;justify-content:space-between;font-size:15px;color:var(--ui-text-highlighted)">{{ m.t }}<span aria-hidden="true" style="display:inline-block;width:18px;height:18px;background-color:currentColor;opacity:{{ m.op }};-webkit-mask:url('https://api.iconify.design/lucide/check.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/check.svg') center / contain no-repeat"></span></div></sc-for><div style="height:1px;background:var(--ui-border);margin:4px 0"></div><div data-act="logout" role="menuitem" style="height:48px;padding:0 16px;display:flex;align-items:center;gap:12px;font-size:15px;color:var(--ui-text-highlighted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:18px;height:18px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/log-out.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/log-out.svg') center / contain no-repeat"></span>Log out</div></div></div></sc-if>
<sc-if value="{{ s.overlay }}"><div role="status" aria-live="polite" style="position:absolute;inset:0;z-index:70;background:var(--ui-bg-muted);display:flex;align-items:center;justify-content:center;padding:24px"><div style="width:100%;max-width:440px;display:flex;flex-direction:column;gap:16px;align-items:flex-start"><svg width="26" height="26" viewBox="0 0 16 16" shape-rendering="crispEdges" aria-hidden="true"><rect width="16" height="16" fill="#111"></rect><path fill="#fff" fill-rule="evenodd" d="M4 3h6v1h1v1h1v3h-1v1h-1v1h1v1h1v2h-2v-2h-1v-1H6v3H4zm2 2v3h4V5z"></path></svg><div style="display:flex;flex-direction:column;gap:6px"><span style="font:600 22px/30px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">{{ s.overlay.title }}</span><span style="font-size:14px;line-height:22px;color:var(--ui-text-toned);text-wrap:pretty">{{ s.overlay.text }}</span></div><div role="progressbar" aria-label="Restarting" style="width:100%;height:4px;background:var(--ui-bg-accented)"><div style="width:35%;height:4px;margin-left:{{ s.overlay.barL }};background:var(--ui-primary)"></div></div><span style="font-size:12px;color:var(--ui-text-muted)">{{ s.overlay.meta }}</span><sc-if value="{{ s.overlay.btn }}"><span role="button" style="height:{{ btnH }};padding:0 14px;display:flex;align-items:center;gap:6px;border:1px solid var(--ui-border-accented);background:var(--ui-bg);color:var(--ui-text-highlighted);font-weight:500">{{ s.overlay.btn }}</span></sc-if></div></div></sc-if>
<sc-if value="{{ s.modal }}">
<div style="position:absolute;inset:0;z-index:50;background:var(--rc-overlay);display:flex;align-items:{{ modalAlign }};justify-content:center;padding:{{ modalPad }}">
<div role="dialog" aria-modal="true" style="width:{{ modalW }};background:var(--ui-bg);box-shadow:var(--rc-shadow-modal);display:flex;flex-direction:column">
<sc-if value="{{ mob }}"><div style="display:flex;justify-content:center;padding-top:8px"><span style="display:block;width:36px;height:4px;background:var(--ui-border-accented)"></span></div></sc-if>
<div style="padding:20px 24px 0;display:flex;align-items:flex-start;gap:12px"><div style="flex:1;display:flex;flex-direction:column;gap:4px"><span style="font:600 16px/24px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">{{ s.modal.title }}</span><sc-if value="{{ s.modal.desc }}"><span style="color:var(--ui-text-muted);text-wrap:pretty">{{ s.modal.desc }}</span></sc-if></div><sc-if value="{{ s.modal.close }}"><span data-act="modal-close" style="width:{{ closeSz }};height:{{ closeSz }};display:flex;align-items:center;justify-content:center;color:var(--ui-text-muted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/x.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/x.svg') center / contain no-repeat"></span></span></sc-if></div>
<div style="padding:20px 24px 0;display:flex;flex-direction:column;gap:16px">
<sc-if value="{{ s.modal.notice }}"><div role="alert" style="display:flex;gap:8px;padding:10px 12px;background:var(--rc-error-soft);color:var(--ui-error)"><span style="display:flex;padding-top:2px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/circle-x.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/circle-x.svg') center / contain no-repeat"></span></span><span style="color:var(--ui-text-toned)">{{ s.modal.notice }}</span></div></sc-if>
<sc-if value="{{ s.modal.list }}"><div style="display:flex;flex-direction:column;gap:6px"><sc-for list="{{ mList }}" as="li" hint-placeholder-count="3"><div style="display:flex;gap:8px;align-items:flex-start;color:var(--ui-text-toned)"><span style="display:flex;padding-top:3px;color:var(--ui-error)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/x.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/x.svg') center / contain no-repeat"></span></span><span style="text-wrap:pretty">{{ li.t }}</span></div></sc-for></div></sc-if>
<sc-if value="{{ mWarn }}"><div role="status" style="display:flex;gap:10px;padding:12px 14px;background:{{ mWarn.bg }};color:{{ mWarn.fg }}"><span style="display:flex;padding-top:2px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ mWarn.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ mWarn.icon }}.svg') center / contain no-repeat"></span></span><div style="display:flex;flex-direction:column;gap:2px"><span style="font-weight:600">{{ mWarn.title }}</span><span style="color:var(--ui-text-toned);text-wrap:pretty">{{ mWarn.text }}</span></div></div></sc-if>
<sc-for list="{{ mFields }}" as="f" hint-placeholder-count="0">
<div style="display:flex;flex-direction:column;gap:6px"><div style="display:flex;justify-content:space-between"><span style="font-weight:500;color:var(--ui-text-highlighted)">{{ f.label }}</span><sc-if value="{{ f.count }}"><span style="font:400 12px 'IBM Plex Mono',monospace;color:{{ f.countFg }}">{{ f.count }}</span></sc-if></div><div style="min-height:{{ f.h }};padding:{{ f.p }};display:flex;align-items:{{ f.ai }};border:1px solid {{ f.bd }};box-shadow:{{ f.sh }};background:{{ f.bg }};color:{{ f.fg }};font-size:{{ inF }}">{{ f.shown }}</div><sc-if value="{{ f.err }}"><span style="display:flex;gap:6px;align-items:center;color:var(--ui-error);font-size:12px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/circle-alert.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/circle-alert.svg') center / contain no-repeat"></span>{{ f.err }}</span></sc-if><sc-if value="{{ f.help }}"><span style="color:var(--ui-text-muted);font-size:12px">{{ f.help }}</span></sc-if></div>
</sc-for>
<sc-if value="{{ s.modal.link }}"><span data-act="modal-link" role="link" style="align-self:flex-start;min-height:{{ closeSz }};display:flex;align-items:center;gap:6px;color:var(--ui-text-highlighted);font-weight:500;text-decoration:underline;text-underline-offset:3px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/download.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/download.svg') center / contain no-repeat"></span>{{ s.modal.link }}</span></sc-if>
<sc-if value="{{ s.modal.toggle }}"><div style="display:flex;align-items:center;gap:12px;min-height:{{ inH }}"><span style="width:32px;height:18px;padding:2px;display:flex;justify-content:{{ s.modal.toggle.j }};background:{{ s.modal.toggle.bg }}"><span style="width:14px;height:14px;background:var(--ui-bg)"></span></span><span style="color:var(--ui-text-highlighted);font-weight:500">Open in new tab</span></div></sc-if>
<sc-if value="{{ s.modal.removeLink }}"><div style="display:flex;align-items:center;gap:8px;min-height:{{ inH }};color:var(--ui-error);font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/unlink.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/unlink.svg') center / contain no-repeat"></span>Remove link</div></sc-if>
<sc-if value="{{ s.modal.upload }}">
<div data-act="upload-pick" style="height:180px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:16px;text-align:center;border:{{ up.bd }};background:{{ up.bg }};color:{{ up.fg }}"><span aria-hidden="true" style="display:inline-block;width:24px;height:24px;background-color:{{ up.ic }};-webkit-mask:url('https://api.iconify.design/lucide/{{ up.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ up.icon }}.svg') center / contain no-repeat"></span><span style="font-weight:500;color:var(--ui-text-highlighted)">{{ up.title }}</span><sc-if value="{{ up.progress }}"><div style="width:80%;height:4px;background:var(--ui-bg-accented)"><div style="width:{{ up.progress }};height:4px;background:var(--ui-primary)"></div></div></sc-if><span style="color:var(--ui-text-toned);font-size:12px;max-width:300px">{{ up.text }}</span></div>
</sc-if>
<sc-if value="{{ s.modal.crop }}">
<div style="display:flex;flex-direction:column;gap:16px">
<div style="height:{{ cr.stageH }};padding:16px;background:var(--ui-bg-elevated);display:flex;align-items:center;justify-content:center">
<div role="img" aria-label="north-wall.jpg, 4032 × 3024 px" style="position:relative;height:100%;max-width:100%;aspect-ratio:4/3;overflow:hidden;background:#d8d0c3">
<div style="position:absolute;left:0;right:0;bottom:0;height:32%;background:#8f8373"></div>
<div style="position:absolute;left:56%;top:14%;width:22%;height:42%;background:var(--rc-sand);border:6px solid var(--ui-border-accented)"></div>
<div tabindex="0" aria-label="Crop area. Arrow keys move it, Shift and arrow keys resize it." style="position:absolute;left:{{ cr.l }};top:{{ cr.t }};width:{{ cr.w }};height:{{ cr.h }};box-shadow:0 0 0 2000px var(--rc-overlay);outline:1px solid var(--ui-bg);cursor:move">
<div style="position:absolute;left:33.333%;top:0;bottom:0;width:1px;background:rgb(255 255 255 / 0.45)"></div>
<div style="position:absolute;left:66.666%;top:0;bottom:0;width:1px;background:rgb(255 255 255 / 0.45)"></div>
<div style="position:absolute;top:33.333%;left:0;right:0;height:1px;background:rgb(255 255 255 / 0.45)"></div>
<div style="position:absolute;top:66.666%;left:0;right:0;height:1px;background:rgb(255 255 255 / 0.45)"></div>
<div style="position:absolute;left:-6px;top:-6px;width:12px;height:12px;background:var(--ui-bg);border:1px solid var(--ui-primary)"></div>
<div style="position:absolute;right:-6px;top:-6px;width:12px;height:12px;background:var(--ui-bg);border:1px solid var(--ui-primary)"></div>
<div style="position:absolute;left:-6px;bottom:-6px;width:12px;height:12px;background:var(--ui-bg);border:1px solid var(--ui-primary)"></div>
<div style="position:absolute;right:-6px;bottom:-6px;width:12px;height:12px;background:var(--ui-bg);border:1px solid var(--ui-primary)"></div>
<span style="position:absolute;left:8px;bottom:8px;padding:1px 6px;background:var(--ui-bg-inverted);color:var(--ui-text-inverted);font:400 11px/18px 'IBM Plex Mono',monospace;white-space:nowrap">{{ cr.dims }}</span>
</div>
</div>
</div>
<div style="display:flex;flex-direction:column;gap:6px">
<div style="display:flex;align-items:center;gap:4px"><span style="flex:1;font-weight:500;color:var(--ui-text-highlighted)">Aspect ratio</span><span data-act="crop-rotate" style="height:{{ btnH }};padding:0 8px;display:flex;align-items:center;gap:6px;color:var(--ui-text-toned);font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/rotate-ccw-square.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/rotate-ccw-square.svg') center / contain no-repeat"></span>Rotate</span><span data-act="crop-reset" style="height:{{ btnH }};padding:0 8px;display:flex;align-items:center;gap:6px;color:var(--ui-text-toned);font-weight:500"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/undo-2.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/undo-2.svg') center / contain no-repeat"></span>Reset</span></div>
<div role="radiogroup" aria-label="Aspect ratio" style="display:flex;border:1px solid var(--ui-border-accented);width:{{ cr.groupW }}">
<sc-for list="{{ cr.ratios }}" as="p" hint-placeholder-count="5"><span role="radio" aria-checked="{{ p.on }}" data-act="{{ p.act }}" style="flex:{{ cr.pillFlex }};height:{{ btnH }};padding:0 12px;display:flex;align-items:center;justify-content:center;font-weight:500;white-space:nowrap;background:{{ p.bg }};color:{{ p.fg }}">{{ p.t }}</span></sc-for>
</div>
</div>
<div style="display:flex;flex-direction:column;gap:6px">
<span style="font-weight:500;color:var(--ui-text-highlighted)">Width</span>
<div role="radiogroup" aria-label="Width" style="display:flex;border:1px solid var(--ui-border-accented);width:{{ cr.groupW }}">
<sc-for list="{{ cr.sizes }}" as="p" hint-placeholder-count="4"><span role="radio" aria-checked="{{ p.on }}" data-act="{{ p.act }}" style="flex:{{ cr.pillFlex }};height:{{ btnH }};padding:0 12px;display:flex;align-items:center;justify-content:center;font-weight:500;white-space:nowrap;background:{{ p.bg }};color:{{ p.fg }}">{{ p.t }}</span></sc-for>
</div>
<span style="font:400 12px/18px 'IBM Plex Mono',monospace;color:var(--ui-text-muted)">{{ cr.out }}</span>
</div>
<sc-if value="{{ cr.note }}"><div role="status" style="display:flex;gap:8px;padding:10px 12px;background:var(--rc-info-soft)"><span style="display:flex;padding-top:2px;color:var(--ui-info)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/info.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/info.svg') center / contain no-repeat"></span></span><span style="color:var(--ui-text-toned);text-wrap:pretty">{{ cr.note }}</span></div></sc-if>
</div>
</sc-if>
</div>
<div style="padding:20px 24px;display:flex;flex-direction:{{ mBtnDir }};justify-content:flex-end;gap:8px">
<sc-if value="{{ s.modal.cancel }}"><span data-act="modal-cancel" style="height:{{ btnH }};padding:0 12px;display:flex;align-items:center;justify-content:center;border:1px solid var(--ui-border-accented);color:{{ mCancelFg }};font-weight:500">{{ s.modal.cancel }}</span></sc-if>
<sc-if value="{{ s.modal.confirm }}"><span data-act="modal-confirm" style="height:{{ btnH }};padding:0 12px;display:flex;align-items:center;justify-content:center;gap:6px;background:{{ mConfirmBg }};color:var(--ui-bg);font-weight:500;opacity:{{ mConfirmOp }};text-align:center"><sc-if value="{{ s.modal.loading }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/loader-circle.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/loader-circle.svg') center / contain no-repeat"></span></sc-if>{{ s.modal.confirm }}</span></sc-if>
</div>
</div>
</div>
</sc-if>
{{ purgeDlg }}
<sc-if value="{{ navDrawer }}"><div data-act="mnav-close" style="position:absolute;inset:0;z-index:60;display:flex;background:var(--rc-overlay)"><div role="navigation" aria-label="Main" style="width:300px;max-width:85%;height:100%;background:var(--ui-bg);box-shadow:var(--rc-shadow-modal);display:flex;flex-direction:column;padding:12px;gap:2px"><div style="display:flex;align-items:center;gap:10px;height:40px;padding:0 6px;margin-bottom:10px"><svg width="26" height="26" viewBox="0 0 16 16" shape-rendering="crispEdges"><rect width="16" height="16" fill="#111"></rect><path fill="#fff" fill-rule="evenodd" d="M4 3h6v1h1v1h1v3h-1v1h-1v1h1v1h1v2h-2v-2h-1v-1H6v3H4zm2 2v3h4V5z"></path></svg><span style="flex:1;font-weight:600;font-size:14px;color:var(--ui-text-highlighted)">RenewCMS</span></div><sc-for list="{{ nav }}" as="n" hint-placeholder-count="3"><div data-act="nav-{{ n.key }}" style="display:flex;align-items:center;gap:10px;height:44px;padding:0 10px;background:{{ n.bg }};color:{{ n.fg }};font-weight:{{ n.fw }}"><span aria-hidden="true" style="display:inline-block;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ n.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ n.icon }}.svg') center / contain no-repeat"></span><span style="flex:1">{{ n.t }}</span><sc-if value="{{ n.chev }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ n.chev }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ n.chev }}.svg') center / contain no-repeat"></span></sc-if></div><sc-if value="{{ n.sub }}"><div style="display:flex;flex-direction:column;gap:2px;margin-left:19px;border-left:1px solid var(--ui-border)"><span style="padding:6px 10px 2px 12px;font-size:11px;line-height:16px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--ui-text-muted)">Media</span><sc-for list="{{ n.subs }}" as="x" hint-placeholder-count="2"><div data-act="nav-{{ x.t }}" style="display:flex;align-items:center;gap:10px;height:44px;padding:0 10px 0 12px;background:{{ x.bg }};color:{{ x.fg }};font-weight:{{ x.fw }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ x.ic }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ x.ic }}.svg') center / contain no-repeat"></span>{{ x.t }}</div></sc-for><sc-if value="{{ n.hasSites }}"><span style="padding:6px 10px 2px 12px;font-size:11px;line-height:16px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--ui-text-muted)">Publishing</span></sc-if><sc-for list="{{ n.subs2 }}" as="x2" hint-placeholder-count="1"><div data-act="nav-{{ x2.t }}" style="display:flex;align-items:center;gap:10px;height:44px;padding:0 10px 0 12px;background:{{ x2.bg }};color:{{ x2.fg }};font-weight:{{ x2.fw }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ x2.ic }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ x2.ic }}.svg') center / contain no-repeat"></span>{{ x2.t }}</div></sc-for><sc-if value="{{ n.hasAccess }}"><span style="padding:6px 10px 2px 12px;font-size:11px;line-height:16px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--ui-text-muted)">Access</span></sc-if><sc-for list="{{ n.subs3 }}" as="x3" hint-placeholder-count="1"><div data-act="nav-{{ x3.t }}" style="display:flex;align-items:center;gap:10px;height:44px;padding:0 10px 0 12px;background:{{ x3.bg }};color:{{ x3.fg }};font-weight:{{ x3.fw }}"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/{{ x3.ic }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ x3.ic }}.svg') center / contain no-repeat"></span>{{ x3.t }}</div></sc-for></div></sc-if></sc-for><sc-if value="{{ contentShow }}"><div style="display:flex;flex-direction:column;gap:2px;margin-top:8px"><span style="padding:6px 10px 2px;font-size:11px;line-height:16px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--ui-text-muted)">Content</span><sc-if value="{{ contentColl }}"><div style="display:flex;align-items:center;gap:10px;height:44px;padding:0 10px;color:var(--ui-text-toned)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/layers.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/layers.svg') center / contain no-repeat"></span><span style="flex:1">All content types</span><span stLine truncated
<sc-if value="{{ lk.sheet }}"><div style="position:absolute;inset:0;z-index:55;background:var(--rc-overlay);display:flex;align-items:flex-end"><div role="dialog" aria-label="Link" style="width:100%;background:var(--ui-bg);box-shadow:var(--rc-shadow-modal);display:flex;flex-direction:column"><div style="display:flex;justify-content:center;padding-top:8px"><span style="display:block;width:36px;height:4px;background:var(--ui-border-accented)"></span></div><div style="display:flex;flex-direction:column;gap:2px;padding:12px 16px 12px;border-bottom:1px solid var(--ui-border)"><span style="font-weight:600;font-size:16px;line-height:24px;color:var(--ui-text-highlighted)">timber windows</span><span style="font:400 12px/18px 'IBM Plex Mono',monospace;color:var(--ui-text-toned);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">/articles/winter-checklist</span></div><div style="display:flex;flex-direction:column;padding:8px 0 16px"><span style="height:48px;padding:0 16px;display:flex;align-items:center;gap:12px;color:var(--ui-text-highlighted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:18px;height:18px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/pencil.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/pencil.svg') center / contain no-repeat"></span>Edit link</span><span style="height:48px;padding:0 16px;display:flex;align-items:center;gap:12px;color:var(--ui-text-highlighted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:18px;height:18px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/external-link.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/external-link.svg') center / contain no-repeat"></span>Open link</span><span style="height:48px;padding:0 16px;display:flex;align-items:center;gap:12px;color:var(--ui-error)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:18px;height:18px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/unlink.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/unlink.svg') center / contain no-repeat"></span>Remove link</span></div></div></div></sc-if>
<sc-if value="{{ edSheet }}"><div style="position:absolute;inset:0;z-index:55;background:var(--rc-overlay);display:flex;align-items:flex-end"><div role="dialog" aria-modal="true" aria-label="Details" style="width:100%;height:92%;background:var(--ui-bg);box-shadow:var(--rc-shadow-modal);display:flex;flex-direction:column"><div style="display:flex;justify-content:center;padding-top:8px;flex-shrink:0"><span style="display:block;width:36px;height:4px;background:var(--ui-border-accented)"></span></div><div style="padding:8px 16px 0 16px;display:flex;align-items:center;gap:12px;flex-shrink:0"><span style="flex:1;font:600 16px/24px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">Details</span><span data-act="modal-close" style="width:44px;height:44px;display:flex;align-items:center;justify-content:center;color:var(--ui-text-muted)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/x.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/x.svg') center / contain no-repeat"></span></span></div><div style="flex:1;min-height:0;overflow:hidden;padding:12px 16px 16px;display:flex;flex-direction:column;gap:16px"><div style="display:flex;flex-direction:column;gap:12px;padding-bottom:16px;border-bottom:1px solid var(--ui-border)"><div data-act="meta-toggle" role="button" tabindex="0" aria-expanded="{{ metaOpen }}" aria-controls="meta-body" style="display:flex;align-items:center;gap:8px;min-height:24px;color:var(--ui-text-highlighted)"><span style="display:flex;transform:rotate({{ metaRot }})"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat"></span></span><span style="font-weight:600;font-size:14px">Site and authors</span></div><sc-if value="{{ metaOpen }}"><div id="meta-body" style="display:flex;flex-direction:column;gap:16px">{{ edMeta }}</div></sc-if><sc-if value="{{ metaClosed }}"><span style="padding-left:22px;color:var(--ui-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ metaSummary }}</span></sc-if></div><div style="display:flex;flex-direction:column;gap:12px;padding-bottom:16px;border-bottom:1px solid var(--ui-border)"><div data-act="seo-toggle" role="button" tabindex="0" aria-expanded="{{ seoOpen }}" aria-controls="seo-body" style="display:flex;align-items:center;gap:8px;min-height:24px;color:var(--ui-text-highlighted)"><span style="display:flex;transform:rotate({{ seoRot }})"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/chevron-down.svg') center / contain no-repeat"></span></span><span style="font-weight:600;font-size:14px">Search and sharing</span></div><sc-if value="{{ seoOpen }}"><div id="seo-body" style="display:flex;flex-direction:column;gap:12px">{{ edSeo }}</div></sc-if><sc-if value="{{ seoClosed }}"><span style="padding-left:22px;color:{{ seoFg }};overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ seoSummary }}</span></sc-if></div><div style="display:grid;grid-template-columns:auto 1fr;gap:8px 12px"><span style="color:var(--ui-text-muted)">Created</span><span>12 Mar 2026, 09:14</span><span style="color:var(--ui-text-muted)">Updated</span><span>Today, 11:02</span><span style="color:var(--ui-text-muted)">ID</span><span style="font-family:'IBM Plex Mono',monospace">12</span></div><div style="display:flex;flex-direction:column;gap:6px"><span style="color:var(--ui-text-muted)">Public API URL</span><span style="padding:6px 8px;background:var(--ui-bg-elevated);font:400 12px 'IBM Plex Mono',monospace">/v1/article/12</span></div></div></div></div></sc-if>
<sc-if value="{{ integSearch }}"><dc-import name="IntegrationSearch" s="{{ s.search }}" mobile="{{ mob }}" hint-size="100%,100%"></dc-import></sc-if>
{{ slotOverlays }}
<sc-if value="{{ hasToasts }}">
<div style="position:absolute;z-index:60;display:flex;flex-direction:column;gap:8px;left:{{ tL }};right:{{ tR }};bottom:{{ tB }};width:{{ tW }}">
<sc-for list="{{ toasts }}" as="t" hint-placeholder-count="1">
<div style="display:flex;gap:10px;padding:12px 14px;background:var(--ui-bg);border:1px solid var(--ui-border);box-shadow:var(--rc-shadow-pop)"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;margin-top:2px;width:16px;height:16px;background-color:{{ t.fg }};-webkit-mask:url('https://api.iconify.design/lucide/{{ t.icon }}.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/{{ t.icon }}.svg') center / contain no-repeat"></span><div style="flex:1;display:flex;flex-direction:column;gap:6px"><div style="display:flex;flex-direction:column;gap:2px"><span style="font-weight:600;color:var(--ui-text-highlighted)">{{ t.title }}</span><span style="color:var(--ui-text-muted)">{{ t.text }}</span></div><sc-if value="{{ t.action }}"><span data-act="retry" style="align-self:flex-start;height:28px;padding:0 10px;display:flex;align-items:center;gap:6px;border:1px solid var(--ui-border-accented);color:var(--ui-text-highlighted);font-weight:500;font-size:12px"><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:14px;height:14px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/rotate-cw.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/rotate-cw.svg') center / contain no-repeat"></span>{{ t.action }}</span></sc-if></div><span aria-hidden="true" style="display:inline-block;flex-shrink:0;width:16px;height:16px;background-color:currentColor;-webkit-mask:url('https://api.iconify.design/lucide/x.svg') center / contain no-repeat;mask:url('https://api.iconify.design/lucide/x.svg') center / contain no-repeat"></span></div>
</sc-for>
</div>
</sc-if>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;$preview&quot;:{&quot;width&quot;:1440,&quot;height&quot;:900},&quot;s&quot;:{&quot;editor&quot;:null,&quot;tsType&quot;:&quot;object&quot;},&quot;slotBody&quot;:{&quot;editor&quot;:null,&quot;tsType&quot;:&quot;ReactNode&quot;},&quot;slotOverlays&quot;:{&quot;editor&quot;:null,&quot;tsType&quot;:&quot;ReactNode&quot;},&quot;mobile&quot;:{&quot;editor&quot;:&quot;boolean&quot;,&quot;default&quot;:false,&quot;tsType&quot;:&quot;boolean&quot;}}">
class Component extends DCLogic {
renderVals() {
const s = this.props.s || {}
const m = !!this.props.mobile
const k = s.kind || 'home'
const navItems = s.role === 'user' ? [['home', 'house', 'Home'], ['articles', 'file-text', 'Articles'], ['media', 'images', 'Media'], ['authors', 'feather', 'Authors'], ['activity', 'history', 'Activity']] : [['home', 'house', 'Home'], ['articles', 'file-text', 'Articles'], ['media', 'images', 'Media'], ['authors', 'feather', 'Authors'], ['users', 'users', 'Users'], ['schema', 'shapes', 'Schema'], ['activity', 'history', 'Activity'], ['integration', 'plug', 'Integration'], ['settings', 'settings', 'Settings']]
const cr0 = s.crumbs || [], setOpen = s.nav === 'settings' || !!s.sideOpen, parentOn = (key) => s.nav === key && (key !== 'settings' || cr0.length <= 1)
const nav = navItems.map(([key, icon, t]) => { const on = parentOn(key), open = key === 'settings' && setOpen; return { key, icon, t, bg: on ? 'var(--rc-sand)' : 'transparent', badge: key === 'settings' && !!s.navBadge, fg: on ? 'var(--rc-on-sand)' : open ? 'var(--ui-text-highlighted)' : 'var(--ui-text-toned)', fw: on || open ? 500 : 400, chev: key === 'settings' ? (open ? 'chevron-down' : 'chevron-right') : '', chevML: key === 'settings' && s.navBadge ? '8px' : 'auto', sub: open, hasSites: !s.role || s.role === 'owner', subs2: (!s.role || s.role === 'owner') ? (() => { const o = cr0[cr0.length - 1] === 'Sites'; const q = cr0[cr0.length - 1] === 'Languages'; return [{ t: 'Sites', ic: 'globe', bg: o ? 'var(--rc-sand)' : 'transparent', fg: o ? 'var(--rc-on-sand)' : 'var(--ui-text-toned)', fw: o ? 500 : 400 }, { t: 'Languages', ic: 'languages', bg: q ? 'var(--rc-sand)' : 'transparent', fg: q ? 'var(--rc-on-sand)' : 'var(--ui-text-toned)', fw: q ? 500 : 400 }] })() : [], hasAccess: s.role !== 'user', subs3: s.role === 'user' ? [] : [(() => { const o = cr0[1] === 'Roles'; return { t: 'Roles', ic: 'shield', bg: o ? 'var(--rc-sand)' : 'transparent', fg: o ? 'var(--rc-on-sand)' : 'var(--ui-text-toned)', fw: o ? 500 : 400 } })()], subs: ['Variants', 'Cache'].map(x => { const o = cr0[cr0.length - 1] === x; return { t: x, ic: x === 'Variants' ? 'layers' : 'database', bg: o ? 'var(--rc-sand)' : 'transparent', fg: o ? 'var(--rc-on-sand)' : 'var(--ui-text-toned)', fw: o ? 500 : 400 } }) } })
const CT = s.content || [], ctOn = s.contentOn, ctMax = 5, ctVis = CT.slice(0, Math.max(ctMax, CT.findIndex(x => x.key === ctOn) + 1)), contentRows = (m ? (s.contentOpen ? ctVis : []) : ctVis).map(x => { const on = x.key === ctOn; return { key: x.key, name: x.name, icon: x.icon || 'file-text', bg: on ? 'var(--rc-sand)' : 'transparent', fg: on ? 'var(--rc-on-sand)' : 'var(--ui-text-toned)', fw: on ? 500 : 400 } })
const crumbs = (s.crumbs || []).map((t, i, a) => ({ t, sep: i > 0, fg: i === a.length - 1 ? 'var(--ui-text-highlighted)' : 'var(--ui-text-muted)', fw: i === a.length - 1 ? 500 : 400 }))
const L = s.list || {}
const counts = L.counts || [6, 4, 2]
const tabs = ['All', 'Online', 'Drafts'].map((t, i) => { const on = (L.tab || 0) === i; return { t, n: counts[i], line: on ? 'var(--ui-primary)' : 'transparent', fg: on ? 'var(--ui-text-highlighted)' : 'var(--ui-text-toned)', fw: on ? 500 : 400 } })
const AVT = ['var(--rc-sand)', 'var(--ui-bg-accented)', 'var(--ui-bg-elevated)'], ini2 = n => n.replace(/^Dr\.\s*/, '').split(/\s+/).slice(0, 2).map(x => x[0]).join('').toUpperCase()
const auInfo = a => { if (a.skel) return {}; const au = a.au || []; return { hasAu: au.length > 0, noAu: au.length === 0, avs: au.slice(0, 3).map((n, i) => ({ i: ini2(n), bg: AVT[(n.length + i) % 3], ml: i ? '-6px' : '0' })), auMore: au.length > 3 ? '+' + (au.length - 3) : '', auNames: au.join(', '), auFirst: au.length ? au[0] + (au.length > 1 ? ' +' + (au.length - 1) : '') : 'No authors', site: a.site || '' } }
const rows = (L.loading ? Array.from({ length: 6 }, () => ({ skel: true })) : (L.rows || [])).map(a => ({ ...a, seoSug: a.seo > 0, seoN: a.seo, seoTitle: a.seo + (a.seo === 1 ? ' suggestion' : ' suggestions'), seoHid: a.seo === 'hidden', showOn: !a.busy && a.on, showDraft: !a.busy && a.t && !a.on, op: a.busy ? '0.55' : '1', bg: a.menu ? 'var(--ui-bg-elevated)' : 'transparent', menuBg: a.menu ? 'var(--ui-bg-accented)' : 'transparent', menuPub: a.on ? 'Unpublish' : 'Publish', menuIcon: a.on ? 'globe-lock' : 'globe', ...auInfo(a) }))
const e = s.ed || {}
const saveMap = { clean: ['check', 'Saved', 'var(--ui-text-muted)'], dirty: ['circle-dot', 'Unsaved changes', 'var(--ui-warning)'], saving: ['loader-circle', 'Saving…', 'var(--ui-text-muted)'], saved: ['check', 'Saved at 11:02', 'var(--ui-text-muted)'], error: ['circle-alert', 'Save failed', 'var(--ui-error)'], offline: ['wifi-off', 'Waiting for connection', 'var(--ui-text-muted)'] }
const sv = saveMap[e.save || 'saved']
const save = { icon: sv[0], t: sv[1], fg: sv[2], retry: e.save === 'error' }
const len = e.len ?? 4318
const fmt = n => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
const counter = { t: fmt(Math.round(len / 6)) + ' words · ' + fmt(len) + ' characters', fg: 'var(--ui-text-muted)', icon: '', msg: e.note || '' }
const dirty = e.save === 'dirty' || e.save === 'error'
const online = e.status === 'online'
const blocked = !!s.offline
let pub
if (e.pending) pub = { t: e.pending, icon: 'loader-circle', bg: online ? 'var(--ui-bg)' : 'var(--ui-primary)', fg: online ? 'var(--ui-text-highlighted)' : 'var(--ui-bg)', bd: online ? 'var(--ui-border-accented)' : 'var(--ui-primary)', op: '1' }
else if (online) pub = { t: 'Unpublish', icon: 'globe-lock', bg: 'var(--ui-bg)', fg: 'var(--ui-text-highlighted)', bd: 'var(--ui-border-accented)', op: s.offline ? '0.4' : '1' }
else pub = { t: dirty ? 'Save and publish' : 'Publish', icon: 'globe', bg: 'var(--ui-primary)', fg: 'var(--ui-bg)', bd: 'var(--ui-primary)', op: blocked ? '0.4' : '1' }
const md = s.modal || {}
const mFields = (md.fields || []).map(f => { const has = f.value; return { ...f, shown: has || f.ph || '', fg: md.loading ? 'var(--ui-text-dimmed)' : has ? 'var(--ui-text-highlighted)' : 'var(--ui-text-muted)', bd: f.err ? 'var(--ui-error)' : f.focus ? 'var(--ui-primary)' : 'var(--ui-border-accented)', sh: f.err ? 'inset 0 0 0 1px var(--ui-error)' : f.focus ? 'inset 0 0 0 1px var(--ui-primary)' : 'none', bg: md.loading ? 'var(--ui-bg-elevated)' : 'var(--ui-bg)', h: f.area ? (m ? '120px' : '96px') : (m ? '44px' : '40px'), p: f.area ? '8px 10px' : '0 10px', ai: f.area ? 'flex-start' : 'center', countFg: f.countErr ? 'var(--ui-error)' : 'var(--ui-text-muted)' } })
const U = md.upload || {}
const upMap = {
uploading: { icon: 'image-plus', ic: 'var(--ui-text-toned)', title: 'north-wall.jpg', text: 'Uploading 180 of 420 KB', progress: '43%', bd: '1px solid var(--ui-border)', bg: 'var(--ui-bg)' },
rejected: { icon: 'file-x', ic: 'var(--ui-error)', title: 'north-wall.tif is 14.2 MB', text: 'Images can be up to 10 MB. Use a PNG, JPEG, WEBP or SVG image.', bd: '1px solid var(--ui-error)', bg: 'var(--ui-bg)' },
idle: { icon: 'image-plus', ic: 'var(--ui-text-toned)', title: 'Drop an image here, or choose a file', text: 'PNG, JPEG, WEBP, SVG · max 10 MB', bd: '1px dashed var(--ui-border-accented)', bg: 'var(--ui-bg)' },
error: { icon: 'circle-x', ic: 'var(--ui-error)', title: 'Upload failed', text: 'The server didn\'t accept the file. Your article hasn\'t changed.', bd: '1px solid var(--ui-error)', bg: 'var(--ui-bg)' }
}
const up = upMap[U.state || 'idle'] || upMap.idle
let cr = {}
if (md.crop) {
const C = md.crop, IW = 4032, IH = 3024
const ri = C.ratio || 0, si = C.size ?? 1
const presets = [[0, 0, 100, 100], [6, 8, 88, 78], [0, 12.5, 100, 75], [0, 0, 100, 100], [12.5, 0, 75, 100]]
const r = C.rect || presets[ri]
const cw = Math.round(IW * r[2] / 100), ch = Math.round(IH * r[3] / 100)
const target = [cw, 1600, 1200, 800][si]
const ow = Math.min(target, cw), oh = Math.round(ch * ow / cw)
const kb = Math.round(ow * oh * 0.22 / 1000)
const pill = (pre, cur) => (t, i) => ({ t, on: i === cur, act: pre + i, bg: i === cur ? 'var(--ui-primary)' : 'transparent', fg: i === cur ? 'var(--ui-bg)' : 'var(--ui-text-toned)' })
cr = {
l: r[0] + '%', t: r[1] + '%', w: r[2] + '%', h: r[3] + '%', dims: fmt(cw) + ' × ' + fmt(ch),
ratios: ['Original', 'Free', '16:9', '4:3', '1:1'].map(pill('crop-r-', ri)),
sizes: ['Original', '1600 px', '1200 px', '800 px'].map(pill('crop-s-', si)),
out: 'Uploads as ' + fmt(ow) + ' × ' + fmt(oh) + ' px · about ' + (kb >= 1000 ? (kb / 1000).toFixed(1) + ' MB' : kb + ' KB'),
note: target > cw ? 'The crop is ' + cw + ' px wide, so it\'s uploaded at that size. Enlarging it would make it blurry.' : '',
stageH: m ? '240px' : '340px', pillFlex: m ? '1 1 0' : '0 0 auto', groupW: m ? '100%' : 'max-content'
}
}
const US = s.users || {}
const badges = { accepted: { t: 'Accepted', icon: 'circle-check', bg: 'var(--rc-success-soft)', fg: 'var(--ui-success)', bd: 'var(--rc-success-soft)' }, pending: { t: 'Pending', icon: 'mail', bg: 'var(--rc-warning-soft)', fg: 'var(--ui-warning)', bd: 'var(--rc-warning-soft)' }, expired: { t: 'Expired', icon: 'clock-alert', bg: 'transparent', fg: 'var(--ui-text-muted)', bd: 'var(--ui-border-accented)' } }
const roles = { owner: { t: 'owner', bg: 'var(--ui-primary)', fg: 'var(--ui-bg)', bd: 'var(--ui-primary)' }, admin: { t: 'admin', bg: 'transparent', fg: 'var(--ui-text-toned)', bd: 'var(--ui-border-accented)' } }
const acts = { pending: { key: 'cancel-invite', t: 'Cancel invite', icon: 'user-x', fg: 'var(--ui-error)', op: '1' }, expired: { key: 'renew-invite', t: 'Renew invite', icon: 'refresh-cw', fg: 'var(--ui-text-highlighted)', op: '1' } }
const usRows = (US.loading ? Array.from({ length: 5 }, () => ({ skel: true })) : (US.rows || [])).map(r => { if (r.skel) return r; const dn = (r.you && s.display) || r.display || ''; const a0 = acts[r.st]; return { ...r, dn: dn || '—', dfg: dn ? 'var(--ui-text-highlighted)' : 'var(--ui-text-muted)', display2: !!dn, fg: 'var(--ui-text-highlighted)', rl: roles[r.role] || roles.admin, b: badges[r.st] || badges.pending, bg: r.hl ? 'var(--ui-bg-elevated)' : 'transparent', act: a0 && (r.busy ? { ...a0, t: r.busy, icon: 'loader-circle', op: '0.5' } : a0) } })
const who = s.display || 'marta'
const ini = s.display ? s.display.trim().split(/\s+/).slice(0, 2).map(w => w[0]).join('').toUpperCase() : 'MA'
const pf = s.pf || {}
const dnVal = pf.value ?? s.display ?? ''
const dnLen = [...dnVal].length
const dn = { shown: dnVal || (pf.focus ? '' : 'e.g. Marta Alves'), caret: !!pf.focus, fg: pf.saving ? 'var(--ui-text-dimmed)' : dnVal ? 'var(--ui-text-highlighted)' : 'var(--ui-text-muted)', bd: pf.err ? 'var(--ui-error)' : pf.focus ? 'var(--ui-primary)' : 'var(--ui-border-accented)', sh: pf.err ? 'inset 0 0 0 1px var(--ui-error)' : pf.focus ? 'inset 0 0 0 1px var(--ui-primary)' : 'none', bg: pf.saving ? 'var(--ui-bg-elevated)' : 'var(--ui-bg)', count: dnLen + ' / 60', countFg: dnLen > 60 ? 'var(--ui-error)' : 'var(--ui-text-muted)', err: pf.err || '', foot: pf.dirty && !pf.saving ? 'Unsaved changes' : 'You appear as \u201c' + who + '\u201d' + (s.display ? '.' : ', your username.'), footFg: pf.dirty && !pf.saving ? 'var(--ui-warning)' : 'var(--ui-text-muted)' }
const dnSave = { t: pf.saving ? 'Saving…' : 'Save', loading: !!pf.saving, op: pf.saving || (pf.dirty && !pf.err) ? '1' : '0.4' }
const rs = pf.reset || 'idle'
const rsMap = { sent: { bg: 'var(--rc-success-soft)', fg: 'var(--ui-success)', icon: 'mail-check', title: 'Reset link sent', text: 'Check marta@renew.example. The link works once, within 1 hour.' }, limited: { bg: 'var(--rc-warning-soft)', fg: 'var(--ui-warning)', icon: 'clock-alert', title: 'Too many requests', text: 'You\'ve asked for several links recently. Wait a few minutes, then try again.' }, error: { bg: 'var(--rc-error-soft)', fg: 'var(--ui-error)', icon: 'circle-x', title: 'Couldn\'t send the link', text: 'Check your connection and try again.' } }
const rsNote = rsMap[rs] || null
const rsBtn = { t: rs === 'sending' ? 'Sending…' : rs === 'sent' ? 'Send again' : 'Send reset link', icon: rs === 'sending' ? 'loader-circle' : 'mail', op: rs === 'limited' ? '0.4' : '1' }
const E5 = [['Marta Alves', 'Published the article ', 'Spring release', '2 min ago', 1], ['Marta Alves', 'Edited the article ', 'Why we reuse original floorboards', '29 min ago', 1], ['Not signed in', 'Failed login attempt for ', 'alex@example.com', '1 h ago', 0], ['Jonas Weber', 'Revoked API key ', 'Mobile app', '2 h ago', 1], ['Scheduler', 'Ran the nightly backup', '', '7 h ago', 1]]
const E4 = [['Marta Alves', 'Published the article ', 'Spring release', '2 min ago', 1], ['Marta Alves', 'Edited the article ', 'Why we reuse original floorboards', '29 min ago', 1], ['Marta Alves', 'Deleted the image ', 'old-banner.png', 'Yesterday', 1], ['Marta Alves', 'Uploaded the image ', 'floorboards-copy.jpg', '3 Oct 2026', 1]]
const ho = s.home || {}, own = s.role === 'user'
const actRows = ho.act === 'loading' ? Array.from({ length: 5 }, () => ({ skel: true })) : ho.act === 'ok' ? (own ? E4 : E5).map(([a, t, em, rel, ok]) => ({ s: t, em, meta: own ? rel : a + ' · ' + rel, fail: !ok, ini: a === 'Scheduler' ? 'SY' : a === 'Not signed in' ? '?' : a.split(' ').map(w => w[0]).join('') })) : null
const act = { show: !!ho.act && k === 'home' && !s.boot, title: own ? 'Your recent activity' : 'Recent activity', rows: actRows, error: ho.act === 'error', empty: ho.act === 'empty', emptyText: own ? 'Your actions will appear here.' : 'Actions on your site will appear here.' }
const C = s.cleanup || {}, cOn = !!C.on, cDis = !!(C.ro || C.offline || C.loading), cErr = !!(C.invalid || C.saveErr)
const cl = { loading: !!C.loading, ready: !C.loading, ro: !!C.ro, notRo: !C.ro, on: cOn, saving: !!C.saving, saved: !!C.saved && !C.saving, swW: m ? '44px' : '36px', swH: m ? '44px' : '24px', swOp: cDis ? '0.4' : '1', swRing: C.focusSw ? '2px solid var(--ui-primary)' : '0', swBd: cOn ? 'var(--ui-primary)' : 'var(--ui-border-accented)', swBg: cOn ? 'var(--ui-primary)' : 'var(--ui-bg-accented)', knobL: cOn ? '17px' : '1px', knobBd: cOn ? 'transparent' : 'var(--ui-border-accented)', daysW: m ? '100%' : '140px', daysAlign: m ? 'stretch' : 'flex-end', daysBd: cErr ? 'var(--ui-error)' : C.focusDays ? 'var(--ui-primary)' : 'var(--ui-border-accented)', daysSh: C.focusDays && !cErr ? '0 0 0 1px var(--ui-primary)' : 'none', daysOp: cDis ? '0.4' : '1', daysVal: C.days ?? '30', hasErr: cErr, errTxt: C.invalid ? 'Enter a whole number of days between 1 and 365.' : 'Couldn\'t save. Try again in a moment.', errMax: m ? 'none' : '300px', errTA: m ? 'left' : 'right', lastLine: C.last ? 'Last purge: ' + C.last.d + ' · ' + C.last.c + ' copies merged · ' + C.last.a + ' articles updated' : 'Never run', nextLine: 'Next purge: ' + (C.next || '14 Jul 2026'), purgeOp: C.offline || C.loading ? '0.4' : '1', btnShrink: m ? '1' : '0' }
const R = s.ret || {}, rd = R.days || '90', fv = rd === '0', sz = R.size || '0', hs = sz !== '0'
const rt = { show: !s.noRet, saving: !!R.saving, saved: !!R.saved, err: !!R.err, shorten: !!R.shorten, val: { '30': '30 days', '90': '90 days', '365': '365 days', '0': 'Forever' }[R.shorten ? '30' : rd], help: fv ? (hs ? 'No age limit. Only the size limit applies.' : 'Nothing is removed automatically. The log grows over time.') : (hs ? 'Events older than this are removed.' : 'Older events are removed automatically.'), last: fv && !hs ? 'Last cleanup: Never' : 'Last cleanup: 6 Oct 2026, 03:00 · 142 events removed', next: fv && !hs ? 'Next cleanup: Not scheduled' : 'Next cleanup: 7 Oct 2026, 03:00', sizeVal: hs ? sz + ' GB' : 'No size limit', sizeHelp: hs ? 'When logs pass ' + sz + ' GB, the oldest events are removed. Currently 3.2 GB.' : 'Optional. Logs can grow without a size cap.', selW: m ? '100%' : '160px' }
const purgeDlg = (() => {
if (!s.purge) return null
const h = React.createElement, bh = m ? 44 : 32
const P = t => Object.fromEntries(t.split(';').filter(x => x.trim()).map(x => { const i = x.indexOf(':'); const k = x.slice(0, i).trim(); return [k.startsWith('--') ? k : k.replace(/-([a-z])/g, (_, c) => c.toUpperCase()), x.slice(i + 1).trim()] }))
const D = (t, ...c) => h('div', { style: P(t) }, ...c)
const S = (t, ...c) => h('span', { style: P(t) }, ...c)
const I = (n, z = 16, t = '') => h('span', { 'aria-hidden': 'true', style: { display: 'inline-block', flexShrink: 0, width: z, height: z, backgroundColor: 'currentColor', WebkitMask: 'url(https://api.iconify.design/lucide/' + n + '.svg) center / contain no-repeat', mask: 'url(https://api.iconify.design/lucide/' + n + '.svg) center / contain no-repeat', ...(t ? P(t) : {}) } })
const B = (label, o = {}) => {
const k = o.k || 'outline'
const bg = k === 'primary' ? 'var(--ui-primary)' : k === 'danger' ? 'var(--ui-error)' : 'var(--ui-bg)'
const fg = k === 'primary' || k === 'danger' ? 'var(--ui-bg)' : 'var(--ui-text-highlighted)'
const bd = k === 'primary' || k === 'danger' ? bg : 'var(--ui-border-accented)'
return S('height:' + (o.h || bh) + 'px;padding:0 ' + (o.px || 12) + 'px;display:flex;align-items:center;justify-content:center;gap:6px;background:' + bg + ';color:' + fg + ';border:1px solid ' + bd + ';font-weight:500;font-size:13px;white-space:nowrap;opacity:' + (o.dis ? 0.4 : 1) + ';' + (o.full ? 'flex:1;' : ''), o.icon && I(o.icon, 16), label)
}
const CHECK = 'conic-gradient(var(--ui-bg-accented) 25%, var(--ui-bg) 0 50%, var(--ui-bg-accented) 0 75%, var(--ui-bg) 0) 0 0/16px 16px'
const TONES = ['var(--rc-sand)', 'var(--ui-bg-accented)', 'color-mix(in srgb, var(--ui-border-accented) 35%, var(--ui-bg))', 'var(--ui-bg-elevated)']
const mark = h('svg', { width: 20, height: 20, viewBox: '0 0 16 16', shapeRendering: 'crispEdges' }, h('rect', { width: 16, height: 16, fill: '#111' }), h('path', { fill: '#fff', fillRule: 'evenodd', d: 'M4 3h6v1h1v1h1v3h-1v1h-1v1h1v1h1v2h-2v-2h-1v-1H6v3H4zm2 2v3h4V5z' }))
const Th = (im, t = '') => D('position:relative;overflow:hidden;display:flex;align-items:center;justify-content:center;color:var(--ui-text-muted);background:' + (im.alpha ? CHECK : 'repeating-linear-gradient(135deg, var(--ui-border) 0 1px, transparent 1px 12px), ' + TONES[im.tone % 4]) + ';' + t, im.svg ? mark : I('image', 16, 'opacity:0.55'))
const IMX = { 12: { tone: 0 }, 7: { tone: 2 }, 2: { tone: 1 }, 4: { tone: 3, alpha: true, svg: true }, 11: { tone: 1 } }
const Alert = (tone, icon, text, title) => D('display:flex;gap:10px;padding:12px 14px;background:var(--rc-' + tone + '-soft);color:var(--ui-' + tone + ')', S('display:flex;padding-top:2px', I(icon)), D('flex:1;display:flex;flex-direction:column;gap:2px', title && S('font-weight:600', title), S('color:var(--ui-text-toned)', text)))
const hl = 'color:var(--ui-text-highlighted)'
const mono = "font-family:'IBM Plex Mono',monospace"
const SkelBar = (w, hh = 13) => S('display:block;width:' + w + ';height:' + hh + 'px;background:var(--ui-bg-accented)')
// ---------- dialog
const G = [{ im: 12, n: 'floorboards.jpg', u: 4, c: 2 }, { im: 7, n: 'IMG_4471.jpg', u: 2, c: 1 }, { im: 2, n: 'kitchen-after.webp', u: 3, c: 3 }, { im: 4, n: 'logo-renew.svg', u: 5, c: 3 }, { im: 11, n: 'lime-plaster.jpg', u: 2, c: 1 }]
const pl = (n, w) => n + ' ' + w + (n === 1 ? '' : 's')
const PV = { groups: 4, copies: 9, arts: 12, list: G.slice(0, 4), more: 0, ...(s.purge && s.purge.pv) }
const summary = pv => pl(pv.groups, 'group') + ' · ' + pv.copies + (pv.copies === 1 ? ' copy' : ' copies') + ' will be deleted · ' + pl(pv.arts, 'article') + ' will be updated'
const GList = pv => D('border:1px solid var(--ui-border)', ...pv.list.map((g, i) => D('display:flex;align-items:center;gap:12px;padding:8px 12px;border-top:' + (i ? '1px solid var(--ui-border-muted)' : '0'), Th(IMX[g.im], 'width:40px;height:30px;flex-shrink:0;border:1px solid var(--ui-border)'), D('flex:1;min-width:0;display:flex;flex-direction:column', S('font-weight:500;' + hl + ';overflow:hidden;text-overflow:ellipsis;white-space:nowrap', g.n), S('font-size:12px;color:var(--ui-text-muted)', 'Kept · used in ' + pl(g.u, 'article'))), S('font-size:12px;color:var(--ui-text-toned);white-space:nowrap;text-align:right', pl(g.c, 'copy').replace('copys', 'copies') + ' merged'))), pv.more > 0 && D('padding:8px 12px;border-top:1px solid var(--ui-border-muted);color:var(--ui-text-muted)', 'and ' + pv.more + ' more groups'))
const Link = () => S('display:inline-flex;align-items:center;gap:4px;align-self:flex-start;font-weight:500;' + hl + ';text-decoration:underline;text-underline-offset:3px;min-height:' + (m ? 44 : 20) + 'px', 'Review in Duplicates', I('arrow-right', 14))
const dg = s.purge
const Shell = (o, body) => D('position:absolute;inset:0;z-index:50;background:var(--rc-overlay);display:flex;align-items:' + (m ? 'flex-end' : 'flex-start') + ';justify-content:center;padding:' + (m ? 16 : 140) + 'px 16px 16px',
D('width:' + (m ? '100%' : '448px') + ';max-height:100%;background:var(--ui-bg);box-shadow:var(--rc-shadow-modal);display:flex;flex-direction:column',
o.center ? D('padding:28px 24px 0;display:flex;flex-direction:column;align-items:center;gap:12px;text-align:center', S('width:44px;height:44px;display:flex;align-items:center;justify-content:center;background:var(--rc-info-soft);color:var(--ui-info)', I(o.icon, 20)), S('font:600 16px/24px \'IBM Plex Sans\',sans-serif;' + hl, o.title), S('color:var(--ui-text-muted);text-wrap:pretty;max-width:340px', o.desc))
: D('padding:20px 24px 0;display:flex;flex-direction:column;gap:4px', S('font:600 16px/24px \'IBM Plex Sans\',sans-serif;' + hl, o.title), o.desc && S('color:var(--ui-text-muted);text-wrap:pretty', o.desc)),
body && D('padding:20px 24px 0;display:flex;flex-direction:column;gap:16px;min-height:0;overflow:hidden', ...body),
D('padding:20px 24px;display:flex;flex-direction:' + (m ? 'column-reverse' : 'row') + ';justify-content:flex-end;gap:8px;flex-shrink:0', o.cancel && B(o.cancel, { dis: o.loading, full: false }), o.confirm && S('height:' + bh + 'px;padding:0 12px;display:flex;align-items:center;justify-content:center;gap:6px;background:var(--ui-error);color:var(--ui-bg);font-weight:500;opacity:' + (o.cdis ? 0.4 : 1), o.loading ? I('loader-circle') : o.cicon && I(o.cicon), o.confirm))))
const DESC = 'Articles that use a duplicate will be switched to the most used copy of that image, and the other copies will be deleted. This can\'t be undone.'
const DSkel = () => [SkelBar('80%', 16), D('border:1px solid var(--ui-border)', ...[0, 1, 2].map(i => D('display:flex;align-items:center;gap:12px;padding:8px 12px;border-top:' + (i ? '1px solid var(--ui-border-muted)' : '0'), SkelBar('40px', 30), D('flex:1;display:flex;flex-direction:column;gap:6px', SkelBar('45%'), SkelBar('60%', 11)))))]
const Dialog = () => {
const k = dg.k, pv = PV
const gp = 'Purge ' + pl(pv.groups, 'group')
const note = (tone, icon, t) => Alert(tone, icon, t)
if (k === 'loading') return Shell({ title: 'Purge duplicates?', desc: DESC, cancel: 'Cancel', confirm: 'Purge duplicates', cdis: true }, DSkel())
if (k === 'nothing') return Shell({ center: true, icon: 'copy', title: 'Nothing to purge', desc: 'No duplicate is used in an article, so there is nothing to merge.', cancel: 'Close' }, null)
const body = [S('font-weight:500;' + hl, summary(pv)), GList(pv), Link()]
if (k === 'confirm') return Shell({ title: 'Purge duplicates?', desc: DESC, cancel: 'Cancel', confirm: gp }, body)
if (k === 'progress') return Shell({ title: 'Purge duplicates?', desc: DESC, cancel: 'Cancel', confirm: 'Purging…', loading: true }, body)
if (k === 'error') return Shell({ title: 'Purge duplicates?', desc: DESC, cancel: 'Cancel', confirm: gp }, [note('error', 'circle-x', 'Couldn\'t purge the duplicates. Nothing was changed. Try again in a moment.'), ...body])
if (k === 'partial') return Shell({ title: 'Purge duplicates?', desc: DESC, cancel: 'Close', confirm: 'Retry', cicon: 'rotate-cw' }, [note('warning', 'triangle-alert', 'Some groups couldn\'t be merged. 3 of 4 groups were purged. Try again to finish.'), ...body])
if (k === 'conflict') return Shell({ title: 'Purge duplicates?', desc: DESC, cancel: 'Cancel', confirm: gp }, [note('warning', 'triangle-alert', 'The library changed, so nothing was purged. Check the new summary and try again.'), ...body])
}
return Dialog()
})()
const toasts = (s.toasts || []).map(t => ({ ...t, fg: 'var(--ui-' + t.tone + ')' }))
const endpoints = [
{ path: '/v1/article/', desc: 'Lists every online article.', l1: 'var(--ui-primary)', code: "const res = await fetch('https://cms.renew.example/v1/article/')\nconst articles = await res.json()" },
{ path: '/v1/article/{id}', desc: 'Returns one online article. Drafts and unknown ids both return 404.', l1: 'var(--ui-primary)', code: "const res = await fetch('https://cms.renew.example/v1/article/12')\nif (res.status === 404) return notFound()\nconst article = await res.json()" }
]
const toolbarDef = [['undo-2'], ['redo-2'], '|', ['pilcrow', 'Paragraph ▾'], '|', ['bold', '', true], ['italic'], ['underline'], ['strikethrough'], '|', ['align-left', '', true], ['align-center'], ['align-right'], ['align-justify'], '|', ['list'], ['list-ordered'], ['indent-decrease'], ['indent-increase'], '|', ['link'], ['remove-formatting'], ['image-plus', 'Image']]
const LK = e.lk || {}
const lbs = LK.btn || 'default'
const toolbar = toolbarDef.map(x => { if (x === '|') return { sep: true, btn: false }; const base = { sep: false, btn: true, icon: x[0], text: x[1] || '', bg: x[2] ? 'var(--ui-bg-accented)' : 'transparent', fg: 'var(--ui-text-toned)', op: '1', ring: 'none', tip: '' }; if (x[0] !== 'link') return base; return { ...base, bg: lbs === 'hover' ? 'var(--ui-bg-elevated)' : lbs === 'active' ? 'var(--rc-sand)' : 'transparent', fg: lbs === 'hover' ? 'var(--ui-text-highlighted)' : lbs === 'active' ? 'var(--rc-on-sand)' : 'var(--ui-text-toned)', op: lbs === 'disabled' ? '0.4' : '1', ring: lbs === 'focus' ? '2px solid var(--ui-primary)' : 'none', tip: lbs === 'hover' && !m ? 'Insert link (Ctrl+K)' : '' } })
const lkT = LK.text || 'default'
const lk = { on: !!LK.on, pop: !!LK.pop && !m, sheet: !!LK.pop && m, popEdit: 'transparent', style: 'color:' + (lkT === 'selected' ? 'var(--rc-on-sand)' : 'var(--ui-text-highlighted)') + ';text-decoration:underline;text-decoration-thickness:' + (lkT === 'hover' ? '2px' : '1px') + ';text-underline-offset:3px;' + (lkT === 'selected' ? 'background:var(--rc-sand);' : '') + (lkT === 'focus' ? 'outline:2px solid var(--ui-primary);outline-offset:2px;' : '') }
const LG = e.lang || null
if (LG && !e.pending && pub) pub = { ...pub, t: pub.t.replace('Save and publish', 'Save and publish ' + LG.name).replace(/^Publish$/, 'Publish ' + LG.name).replace(/^Unpublish$/, 'Unpublish ' + LG.name) }
const lg = LG ? { name: LG.name, code: LG.code, open: !!LG.open, ro: !!LG.ro, fresh: LG.fresh || '', bd: LG.open ? 'var(--ui-primary)' : 'var(--ui-border-accented)', h: m ? '44px' : '26px', label: 'Language: ' + LG.name + '. Switch version' } : null
const edReady = k === 'editor' && !e.loading && !e.notfound
const edTitle = e.title || 'How we price a bathroom renovation'
return {
lg, srcPane: this.props.srcPane || null, edBanner: this.props.edBanner || null, edTitleCss: LG ? 'flex:0 1 100%;min-width:0' : '', edTitleNote: LG ? 'Each language has its own title. Changing it doesn\'t change the URL; edit the slug in Search and sharing.' : "Titles can't be changed after an article is created. To use a different title, create a new article and delete this one.", edTitleTip: LG ? 'Titles can be changed. The URL only changes if you edit the slug.' : "The title can't be changed after creation.", delText: LG ? 'Delete ' + LG.name + ' version' : 'Delete',
lk, edSeo: this.props.edSeo || null, seoOpen: !!this.props.edSeo, seoClosed: !this.props.edSeo, seoRot: this.props.edSeo ? '0deg' : '-90deg', seoSummary: e.seoSummary || 'Ready', seoFg: /suggest/.test(e.seoSummary || '') ? 'var(--ui-warning)' : 'var(--ui-text-muted)', naOn: !!L.needs, naBg: L.needs ? 'var(--rc-sand)' : 'var(--ui-bg)', naFg: L.needs ? 'var(--rc-on-sand)' : 'var(--ui-text-toned)', naBd: L.needs ? 'var(--rc-sand)' : 'var(--ui-border-accented)', naFw: L.needs ? 500 : 400, naN: L.naState === 'pending' ? '…' : L.naState === 'fail' ? '!' : L.naState === 'absent' ? '' : String(L.needsN ?? 3), edMeta: this.props.edMeta || null, metaOpen: !!this.props.edMeta, metaClosed: !this.props.edMeta, metaRot: this.props.edMeta ? '0deg' : '-90deg', metaSummary: e.metaSummary || 'Inès Duarte', edSheet: m && !!e.sheet, pAu: (() => { const q = s.pa || {}; return { none: q.k !== 'has', has: q.k === 'has', err: !!q.err, slug: q.slug || 'marta-alves', slugNote: !!q.slug, btn: q.loading ? 'Creating…' : 'Create my author profile', icon: q.loading ? 'loader-circle' : 'feather', op: q.loading ? 0.7 : 1, uses: q.user ? 'Uses your username, marta. You can change it afterwards.' : 'Uses your display name, Marta Alves. You can change it afterwards.' } })(), showSites: !s.role || s.role === 'owner', authorsCol: L.authors !== false, multi: !!L.multi, multiDesk: !m && !!L.multi, multiMob: m && !!L.multi, gridCols: (L.authors !== false ? 'minmax(0,1fr) 104px ' : 'minmax(0,1fr) ') + (L.multi ? '130px ' : '') + '110px 150px 140px 44px', sfText: L.site || 'All sites', sfFg: L.site ? 'var(--ui-text-highlighted)' : 'var(--ui-text-muted)', sfMenu: !!L.siteMenu, sfOpts: ['All sites', 'Renew', 'Journal', 'Projects', 'Archive'].map(x => { const on = (L.site || 'All sites') === x; return { t: x, on, op: on ? 1 : 0, bg: on ? 'var(--ui-bg-elevated)' : 'transparent' } }), tbOverflow: m ? 'hidden' : 'visible', tbShift: m ? -(LK.shift || 0) + 'px' : '0px', tbFadeL: m && !!LK.shift, tbFadeR: m && !LK.end, act, cl, rt, purgeDlg, rowDir: m ? 'column' : 'row', rowAlign: m ? 'stretch' : 'flex-start', rowJust: m ? 'flex-start' : 'space-between', rowGap: m ? '12px' : '16px', setScroll: -(s.scrollY || 0) + 'px', offText: s.offText || 'You\'re offline. You can keep writing, but saving and publishing will wait until you\'re back online.', cr, s, nav, crumbs, who, ini, pf, dn, dnSave, rsNote, rsBtn,
langSeg: [['en','English'],['fr','Français']].map(([k,tt])=>{const on=(s.lang||'en')===k;return {act:'lang-'+k,code:k,t:tt,on,op:on?1:0}}), themeSeg: [['system','monitor','System'],['light','sun','Light'],['dark','moon','Dark']].map(([k,icon,tt])=>{const on=(s.mode||'light')===k;return {act:'theme-'+k,icon,t:tt,on,op:on?1:0}}), menuOpen: !!s.userMenu, deskMenu: !m && !!s.userMenu, mobMenu: m && !!s.userMenu, pfItemBg: 'var(--ui-bg-elevated)',
chipBg: s.userMenu || s.nav === 'profile' ? 'var(--rc-sand)' : 'transparent', chipFg: s.userMenu || s.nav === 'profile' ? 'var(--rc-on-sand)' : 'var(--ui-text-highlighted)',
isSettings: k === 'settings', isProfile: k === 'profile', pfLoading: k === 'profile' && !!pf.loading, pfReady: k === 'profile' && !pf.loading,
upBtnDir: m ? 'column' : 'row', upBtnAlign: m ? 'stretch' : 'flex-start', cardPad: '16px', cardPadX: m ? '16px' : '20px', inH: m ? '44px' : '40px', pfCols: m ? 'minmax(0,1fr)' : 'minmax(0,1fr) minmax(0,1.4fr)', pwDir: m ? 'column' : 'row', pwAlign: m ? 'stretch' : 'flex-end', edTitle, edBody: e.body || '', sampleBody: !e.body, tabs, rows, save, counter, pub, mFields, up, toasts, endpoints, toolbar,
contentShow: CT.length > 0, contentRows, contentColl: m && !s.contentOpen, contentCount: String(CT.length), contentMore: (!m || s.contentOpen) && CT.length > ctVis.length ? 'Show all ' + CT.length : '', desk: !m, mob: m, notBoot: !s.boot,
fw: m ? '390px' : '1440px', fh: m ? '844px' : '900px', dir: m ? 'column' : 'row',
mLead: s.mLead || 'menu',
navDrawer: m && !!s.drawer, integSearch: k === 'integration' && !!s.search, isSlot: !!this.props.slotBody, slotBody: this.props.slotBody || null, slotOverlays: this.props.slotOverlays || null, isBoot: !!s.boot, isHome: k === 'home' && !s.boot, isList: k === 'articles', isEditor: k === 'editor', isInteg: k === 'integration', isKeys: k === 'apikeys', isError: k === 'error', isUsers: k === 'users',
usRows, usError: !!US.error, usOnly: !!US.only && !US.error, usTable: !m && !US.error, usCards: m && !US.error,
pad: m ? '16px 16px 88px' : '24px', gap: '16px', cols3: m ? 'repeat(3,minmax(0,1fr))' : 'repeat(3,minmax(0,1fr))',
upBannerAlign: m ? 'stretch' : 'center', upCmdDir: m ? 'column' : 'row', btnH: m ? '44px' : '32px', rowH: m ? '52px' : '44px', inF: m ? '16px' : '14px',
tbDir: m ? 'column' : 'row', tbGap: m ? '12px' : '16px', tbBorder: m ? '0' : '1px solid var(--ui-border)', tabBorder: m ? '1px solid var(--ui-border)' : '0', tabH: m ? '44px' : '36px', tabF: m ? '14px' : '13px',
qText: L.q ? L.q : 'Search by title', qFg: L.q ? 'var(--ui-text-highlighted)' : 'var(--ui-text-muted)',
pgBtn: m ? '44px' : '32px', ...(() => {
const g = L.pg
if (!g) return { pager: null, pagerD: false, pagerM: false, pgErr: '', pgNote: '' }
const mk = (t, cur) => ({ t: String(t), cur: cur ? 'page' : 'false', bg: cur ? 'var(--rc-sand)' : 'var(--ui-bg)', fg: cur ? 'var(--rc-on-sand)' : 'var(--ui-text-highlighted)', bd: cur ? 'var(--ui-primary)' : 'var(--ui-border-accented)', fw: cur ? 600 : 400, ring: g.focus === 'p' + t ? '2px solid var(--ui-primary)' : 'none' })
const set = new Set([1, g.pages, g.page - 1, g.page, g.page + 1].filter(n => n >= 1 && n <= g.pages))
const arr = g.pages <= 7 ? Array.from({ length: g.pages }, (_, i) => i + 1) : [...set].sort((x, y) => x - y)
const nums = []
arr.forEach((n, i) => { if (i && n - arr[i - 1] > 1) nums.push({ t: '…', cur: 'false', bg: 'transparent', fg: 'var(--ui-text-muted)', bd: 'transparent', fw: 400, ring: 'none' }); nums.push(mk(n, n === g.page)) })
const from = g.total ? (g.page - 1) * g.per + 1 : 0, to = Math.min(g.page * g.per, g.total)
const busy = !!g.loading
const fmtN = n => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
const sizes = [5, 25, 50, 100].map(n => ({ t: n + ' per page', on: n === g.per, bg: n === g.per ? 'var(--rc-sand)' : 'transparent', fg: n === g.per ? 'var(--rc-on-sand)' : 'var(--ui-text-highlighted)', fw: n === g.per ? 600 : 400 }))
const pager = { summary: busy ? 'Loading page ' + g.loading + '…' : 'Showing ' + from + '–' + to + ' of ' + fmtN(g.total), size: g.per, sizes, sizeOpen: !!g.sizeOpen, sizeRing: g.focus === 'size' ? '2px solid var(--ui-primary)' : 'none', ctl: g.pages > 1, nums, of: 'Page ' + g.page + ' of ' + g.pages, backOp: busy || g.page === 1 ? '0.4' : '1', fwdOp: busy || g.page === g.pages ? '0.4' : '1', prevRing: g.focus === 'prev' ? '2px solid var(--ui-primary)' : 'none', nextRing: g.focus === 'next' ? '2px solid var(--ui-primary)' : 'none' }
return { pager, pagerD: !m, pagerM: m, pgErr: g.err || '', pgNote: g.note || '' }
})(),
showTable: !m && !L.empty, showCards: m && !L.empty, listFab: !s.modal,
emptyBg: L.empty && L.empty.primary ? 'var(--ui-primary)' : 'var(--ui-bg)', emptyFg: L.empty && L.empty.primary ? 'var(--ui-bg)' : 'var(--ui-text-highlighted)', emptyBd: L.empty && L.empty.primary ? 'var(--ui-primary)' : 'var(--ui-border-accented)',
edFound: k === 'editor' && !e.notfound, edReady, edPending: edReady && !!e.pending, edOnline: edReady && !e.pending && online, edDraft: edReady && !e.pending && !online,
edHeadPad: m ? '12px 16px' : '14px 24px', edHeadGap: m ? '8px 12px' : '12px', edTitleBasis: m ? '100%' : '200px',
showToolbar: k === 'editor' && !e.notfound && !e.preview,
tbPadX: m ? '8px' : '24px', tbBtn: m ? '40px' : '28px',
edBg: e.preview ? 'var(--ui-bg-muted)' : 'var(--ui-bg)', edPad: m ? '24px 16px' : '40px 48px', edMargin: e.preview ? '0 auto' : '0',
h1F: m ? '28px' : '32px', h2F: m ? '23px' : '26px', pF: m ? '17px' : '18px', caret: !e.preview,
pvJust: e.preview ? 'flex-end' : 'flex-start', pvBg: e.preview ? 'var(--ui-primary)' : 'var(--ui-bg-accented)',
saveOp: (dirty && !blocked) ? '1' : '0.4', imgCount: e.imgs != null ? String(e.imgs) : (e.image ? '4' : '3'), apiNote: online ? 'Live. Your website can read this article.' : 'Returns 404 until the article is online.',
copyIcon: s.copied ? 'check' : 'copy', copyText: s.copied ? 'Copied' : 'Copy', copyFg: s.copied ? 'var(--ui-success)' : 'var(--ui-text-highlighted)',
errPad: m ? '40px 20px' : '64px 48px',
closeSz: m ? '44px' : '28px', modalAlign: m ? 'flex-end' : 'flex-start', modalPad: m ? '0' : (md.crop ? '56px 16px 16px' : '140px 16px 16px'), modalW: m ? '100%' : (md.crop ? '640px' : md.wide ? '560px' : '460px'), mBtnDir: m ? 'column-reverse' : 'row', mConfirmOp: md.disabled ? '0.4' : '1', mList: (md.list || []).map(t => ({ t })), mWarn: md.warn ? { ...md.warn, bg: 'var(--rc-' + md.warn.tone + '-soft)', fg: 'var(--ui-' + md.warn.tone + ')' } : null, pfShift: pf.shift ? '-' + (m ? pf.shift[1] : pf.shift[0]) + 'px' : '0px', pfAc: pf.ac || {}, pfC: s.copy || null, pfPanel: !!(pf.ac && (pf.ac.form || pf.ac.pending)),
mConfirmBg: md.tone === 'danger' ? 'var(--ui-error)' : 'var(--ui-primary)', mCancelFg: md.loading ? 'var(--ui-text-dimmed)' : 'var(--ui-text-highlighted)',
hasToasts: toasts.length > 0, tL: m ? '16px' : 'auto', tR: m ? '16px' : '24px', tB: m ? '16px' : '24px', tW: m ? 'auto' : '356px'
}
}
}
</script>
</body>
</html>
@@ -0,0 +1,90 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<script src="./locale-fixtures.js"></script>
<script src="./locale-spec.js"></script>
<meta name="design_doc_mode" content="canvas">
<link rel="icon" href="brand/favicon.svg">
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&family=Source+Serif+4:ital,opsz,wght@0,8..60,400;0,8..60,600;1,8..60,400&display=swap" rel="stylesheet">
<style>body{margin:0;font-family:'IBM Plex Sans',sans-serif;font-size:13px;line-height:20px}*{box-sizing:border-box}a{color:inherit;text-underline-offset:3px}a:hover{opacity:.8}code{font-family:'IBM Plex Mono',monospace;font-size:12px}</style>
</helmet>
<div ref="{{ rootRef }}" data-screen-label="Back-office screens" style="--ui-bg:#ffffff;--ui-bg-muted:#fafafa;--ui-bg-elevated:#f3f2f0;--ui-bg-accented:#e6e4e0;--ui-bg-inverted:#111111;--ui-text-dimmed:#999999;--ui-text-muted:#666666;--ui-text-toned:#444444;--ui-text:#222222;--ui-text-highlighted:#111111;--ui-text-inverted:#ffffff;--ui-border:#e6e4e0;--ui-border-muted:#f3f2f0;--ui-border-accented:#8a8783;--ui-primary:#111111;--ui-secondary:#635643;--ui-success:#315b33;--ui-info:#34536d;--ui-warning:#6c4700;--ui-error:#882c21;--rc-sand:#e8dfd1;--rc-on-sand:#111111;--rc-success-soft:#eaefea;--rc-info-soft:#ebeef0;--rc-warning-soft:#f0ede6;--rc-error-soft:#f3eae9;--rc-overlay:rgb(10 10 10 / 0.4);--rc-shadow-pop:0 4px 16px rgb(17 17 17 / 0.08), 0 1px 2px rgb(17 17 17 / 0.06);--rc-shadow-modal:0 16px 48px rgb(17 17 17 / 0.18);--rc-canvas:#ecebe8;background:var(--rc-canvas);color:var(--ui-text);padding:64px;display:flex;flex-direction:column;gap:72px;width:max-content;min-width:100%">
<div style="display:flex;align-items:flex-end;gap:32px;max-width:1400px">
<div style="flex:1;display:flex;flex-direction:column;gap:10px">
<h1 style="margin:0;font:600 36px/44px 'IBM Plex Sans',sans-serif;letter-spacing:-0.02em;color:var(--ui-text-highlighted)">Screens: article localization</h1>
<p style="margin:0;max-width:880px;font-size:16px;line-height:26px;color:var(--ui-text-toned);text-wrap:pretty">One story, several language versions. Proposed feature: every API change is labelled as proposed. 1440 px at 50%, 390 px at 75%. The theme switch shows every state in dark mode. Imported localization reference. Product and API corrections are in docs/design/localization.md. Rendered parity and prototype flows remain unverified.</p>
</div>
<div role="radiogroup" aria-label="Theme" style="display:flex;border:1px solid var(--ui-border-accented);background:var(--ui-bg)">
<sc-for list="{{ modes }}" as="m" hint-placeholder-count="3">
<button role="radio" aria-checked="{{ m.on }}" onClick="{{ m.pick }}" style="all:unset;cursor:pointer;height:36px;padding:0 14px;display:flex;align-items:center;font-weight:500;font-size:14px;background:{{ m.bg }};color:{{ m.fg }}" style-focus-visible="outline:2px solid var(--ui-primary);outline-offset:2px">{{ m.label }}</button>
</sc-for>
</div>
</div>
<sc-for list="{{ groups }}" as="g" hint-placeholder-count="7">
<section data-screen-label="{{ g.label }}" style="display:flex;flex-direction:column;gap:28px">
<div style="display:flex;flex-direction:column;gap:6px;max-width:1100px">
<h2 style="margin:0;font:600 28px/36px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">{{ g.title }}</h2>
<div style="display:flex;gap:16px;flex-wrap:wrap;font:400 14px/22px 'IBM Plex Mono',monospace;color:var(--ui-text-toned)"><span>{{ g.route }}</span><span style="color:var(--ui-text-muted)">{{ g.api }}</span></div>
<sc-if value="{{ g.note }}"><p style="margin:4px 0 0;font-size:15px;line-height:24px;color:var(--ui-text-toned);text-wrap:pretty">{{ g.note }}</p></sc-if>
</div>
<div style="display:flex;flex-wrap:wrap;gap:40px;align-items:flex-start">
<sc-for list="{{ g.states }}" as="st" hint-placeholder-count="4">
<div style="display:flex;flex-direction:column;gap:12px">
<div style="display:flex;flex-direction:column;gap:2px;max-width:1040px"><span style="font:600 16px/24px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">{{ st.name }}</span><span style="font-size:13px;color:var(--ui-text-muted)">{{ st.trigger }}</span></div>
<div style="display:flex;gap:24px;align-items:flex-start">
<div style="width:{{ st.dw }};height:{{ st.dh }};overflow:hidden;border:1px solid var(--ui-border);flex-shrink:0"><div style="width:{{ st.fw }};height:{{ st.fh }};zoom:{{ st.z }}"><dc-import name="LocaleFrame" s="{{ st.s }}" mobile="{{ false }}" hint-size="1440px,900px"></dc-import></div></div>
<div style="width:293px;height:633px;overflow:hidden;border:1px solid var(--ui-border);flex-shrink:0"><div style="width:390px;height:844px;zoom:0.75"><dc-import name="LocaleFrame" s="{{ st.s }}" mobile="{{ true }}" hint-size="390px,844px"></dc-import></div></div>
</div>
</div>
</sc-for>
</div>
</section>
</sc-for>
<sc-for list="{{ tables }}" as="t" hint-placeholder-count="3"><section style="display:flex;flex-direction:column;gap:10px;max-width:1400px"><h2 style="margin:0;font:600 28px/36px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">{{ t.title }}</h2><div style="background:var(--ui-bg);border:1px solid var(--ui-border)"><div style="display:grid;grid-template-columns:{{ t.w }};gap:16px;padding:8px 12px;background:var(--ui-bg-elevated);color:var(--ui-text-muted);font-weight:500"><sc-for list="{{ t.cols }}" as="c" hint-placeholder-count="3"><span>{{ c }}</span></sc-for></div><sc-for list="{{ t.rows }}" as="r" hint-placeholder-count="3"><div style="display:grid;grid-template-columns:{{ t.w }};gap:16px;padding:10px 12px;border-top:1px solid var(--ui-border);line-height:20px"><sc-for list="{{ r }}" as="c" hint-placeholder-count="3"><span style="color:var(--ui-text-highlighted);text-wrap:pretty;min-width:0">{{ c }}</span></sc-for></div></sc-for></div></section></sc-for>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;theme&quot;:{&quot;editor&quot;:&quot;enum&quot;,&quot;options&quot;:[&quot;system&quot;,&quot;light&quot;,&quot;dark&quot;],&quot;default&quot;:&quot;system&quot;,&quot;tsType&quot;:&quot;'system' | 'light' | 'dark'&quot;}}">
class Component extends DCLogic {
state = { mode: this.props.theme ?? 'light' }
rootRef = React.createRef()
light = { '--ui-bg': '#ffffff', '--ui-bg-muted': '#fafafa', '--ui-bg-elevated': '#f3f2f0', '--ui-bg-accented': '#e6e4e0', '--ui-bg-inverted': '#111111', '--ui-text-dimmed': '#999999', '--ui-text-muted': '#666666', '--ui-text-toned': '#444444', '--ui-text': '#222222', '--ui-text-highlighted': '#111111', '--ui-text-inverted': '#ffffff', '--ui-border': '#e6e4e0', '--ui-border-muted': '#f3f2f0', '--ui-border-accented': '#8a8783', '--ui-primary': '#111111', '--ui-secondary': '#635643', '--ui-success': '#315b33', '--ui-info': '#34536d', '--ui-warning': '#6c4700', '--ui-error': '#882c21', '--rc-sand': '#e8dfd1', '--rc-on-sand': '#111111', '--rc-success-soft': '#eaefea', '--rc-info-soft': '#ebeef0', '--rc-warning-soft': '#f0ede6', '--rc-error-soft': '#f3eae9', '--rc-overlay': 'rgb(10 10 10 / 0.4)', '--rc-shadow-pop': '0 4px 16px rgb(17 17 17 / 0.08), 0 1px 2px rgb(17 17 17 / 0.06)', '--rc-shadow-modal': '0 16px 48px rgb(17 17 17 / 0.18)', '--rc-canvas': '#ecebe8' }
dark = { '--ui-bg': '#111111', '--ui-bg-muted': '#0a0a0a', '--ui-bg-elevated': '#1a1a1a', '--ui-bg-accented': '#222222', '--ui-bg-inverted': '#fafafa', '--ui-text-dimmed': '#666666', '--ui-text-muted': '#999999', '--ui-text-toned': '#cfccc7', '--ui-text': '#e6e4e0', '--ui-text-highlighted': '#fafafa', '--ui-text-inverted': '#111111', '--ui-border': '#333333', '--ui-border-muted': '#222222', '--ui-border-accented': '#666666', '--ui-primary': '#fafafa', '--ui-secondary': '#d4c5b0', '--ui-success': '#a3caa3', '--ui-info': '#a5c2db', '--ui-warning': '#dfb67d', '--ui-error': '#fba394', '--rc-sand': '#d4c5b0', '--rc-on-sand': '#111111', '--rc-success-soft': '#1f241f', '--rc-info-soft': '#202428', '--rc-warning-soft': '#2a251e', '--rc-error-soft': '#2c201f', '--rc-overlay': 'rgb(0 0 0 / 0.6)', '--rc-shadow-pop': '0 4px 16px rgb(0 0 0 / 0.5)', '--rc-shadow-modal': '0 16px 48px rgb(0 0 0 / 0.6)', '--rc-canvas': '#1c1c1c' }
resolved() {
if (this.state.mode === 'system') return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
return this.state.mode
}
apply() {
const el = this.rootRef.current
if (!el) return
const vars = this.resolved() === 'dark' ? this.dark : this.light
for (const k in vars) el.style.setProperty(k, vars[k])
document.body.style.background = vars['--rc-canvas']
}
componentDidMount() { this.apply() }
componentDidUpdate(p) {
if (p.theme !== this.props.theme && this.props.theme) this.setState({ mode: this.props.theme })
this.apply()
}
groups() { return window.LOC.groups.map(g => ({ ...g, label: g.id + ' ' + g.title, states: g.states.map(a => ({ name: a[0], trigger: a[1], dw: a[4]==='narrow'?'717px':'720px', dh: a[4]==='narrow'?'538px':'450px', fw: a[4]==='narrow'?'1024px':'1440px', fh: a[4]==='narrow'?'768px':'900px', z: a[4]==='narrow'?0.7:0.5, s: { v: a[2], x: a[3] }, nar: a[4] === 'narrow' })) })) }
renderVals() {
const modes = [['system', 'System'], ['light', 'Light'], ['dark', 'Dark']].map(([k, label]) => {
const on = this.state.mode === k
return { label, on, bg: on ? 'var(--ui-primary)' : 'transparent', fg: on ? 'var(--ui-bg)' : 'var(--ui-text-toned)', pick: () => this.setState({ mode: k }) }
})
return { rootRef: this.rootRef, modes, groups: this.groups(), tables: window.LOC.tables }
}
}
</script>
</body>
</html>
File diff suppressed because it is too large. Load diff