Colour palettes and a display settings panel: sepia, فیروزی، نیلگوں، سبز، گلابی (each light and dark); font, palette and light/dark/automatic in one header button
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a37ba82829
commit
e04ed0d4af
@ -30,6 +30,9 @@ const PATHS = {
|
||||
key: '<path d="m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4"/><path d="m21 2-9.6 9.6"/><circle cx="7.5" cy="15.5" r="5.5"/>',
|
||||
image: '<rect width="18" height="18" x="3" y="3" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.1-3.1a2 2 0 0 0-2.828 0L6 21"/>',
|
||||
plus: '<path d="M5 12h14"/><path d="M12 5v14"/>',
|
||||
settings: '<path d="M21 4h-7"/><path d="M10 4H3"/><path d="M21 12h-9"/><path d="M8 12H3"/><path d="M21 20h-5"/><path d="M12 20H3"/><path d="M14 2v4"/><path d="M8 10v4"/><path d="M16 18v4"/>',
|
||||
sun: '<circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/>',
|
||||
moon: '<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>',
|
||||
external: '<path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>',
|
||||
};
|
||||
---
|
||||
|
||||
@ -19,7 +19,7 @@ import Icon from '../components/Icon.astro';
|
||||
<meta name="description" content={description} />
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||
<script is:inline>
|
||||
// font + theme choice applied before paint (Naskh and system theme by default)
|
||||
// font, palette and light/dark applied before paint (Naskh, sepia and the system's light/dark by default)
|
||||
try {
|
||||
// ?font=nastaliq|naskh in the URL sets the choice (shareable links)
|
||||
const f = new URLSearchParams(location.search).get('font');
|
||||
@ -27,6 +27,8 @@ import Icon from '../components/Icon.astro';
|
||||
if (localStorage.getItem('divan-font') === 'nastaliq') document.documentElement.classList.add('nastaliq');
|
||||
const t = localStorage.getItem('divan-theme');
|
||||
if (t) document.documentElement.dataset.theme = t;
|
||||
const pal = localStorage.getItem('divan-palette'); // sepia when unset
|
||||
if (pal) document.documentElement.dataset.palette = pal;
|
||||
} catch (e) {}
|
||||
</script>
|
||||
</head>
|
||||
@ -52,10 +54,26 @@ import Icon from '../components/Icon.astro';
|
||||
{Astro.locals.user
|
||||
? <a class="icon-btn" href="/account" title={`اکاؤنٹ: ${Astro.locals.user.full_name || Astro.locals.user.email}`} aria-label="اکاؤنٹ"><Icon name="user" /></a>
|
||||
: <a class="icon-btn" href={`/signin?next=${encodeURIComponent(Astro.url.pathname)}`} title="لاگ ان" aria-label="لاگ ان"><Icon name="login" /></a>}
|
||||
<label class="font-pick" title="خط"><Icon name="font" size={18} /><select id="font-select" aria-label="خط">
|
||||
<option value="naskh">نسخ</option><option value="nastaliq">نستعلیق</option>
|
||||
</select></label>
|
||||
<button type="button" class="icon-btn" id="theme-toggle" aria-label="روشن یا تاریک" title="روشن یا تاریک"><Icon name="theme" /></button>
|
||||
<details class="display-menu">
|
||||
<summary class="icon-btn" title="خط اور رنگ" aria-label="خط اور رنگ"><Icon name="settings" /></summary>
|
||||
<div class="display-panel">
|
||||
<p>خط</p>
|
||||
<div class="seg"><button type="button" data-font="naskh">نسخ</button><button type="button" data-font="nastaliq" class="f-nastaliq">نستعلیق</button></div>
|
||||
<p>رنگ</p>
|
||||
<div class="swatches">
|
||||
{[['', 'سپیا', '#fbf6ea', '#bf3f2a'], ['teal', 'فیروزی', '#e2f0ee', '#0b4f6c'], ['lapis', 'نیلگوں', '#e6ebf8', '#1d3b8b'],
|
||||
['green', 'سبز', '#e5eede', '#1f5d3a'], ['rose', 'گلابی', '#f5e6ea', '#6b2c5e']].map(([v, name, a, b]) => (
|
||||
<button type="button" data-palette={v} title={name} aria-label={name} style={`--a: ${a}; --b: ${b}`}><span></span>{name}</button>
|
||||
))}
|
||||
</div>
|
||||
<p>روشنی</p>
|
||||
<div class="seg">
|
||||
<button type="button" data-theme-pick="light"><Icon name="sun" size={16} /> روشن</button>
|
||||
<button type="button" data-theme-pick="dark"><Icon name="moon" size={16} /> تاریک</button>
|
||||
<button type="button" data-theme-pick=""><Icon name="theme" size={16} /> خودکار</button>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
{Astro.locals.user && (
|
||||
<form method="post" action="/account" class="inline-form">
|
||||
<input type="hidden" name="act" value="signout" />
|
||||
@ -96,13 +114,24 @@ import Icon from '../components/Icon.astro';
|
||||
</footer>
|
||||
<script is:inline>
|
||||
const root = document.documentElement;
|
||||
// the reading font, picked from a list (each name shown in its own font)
|
||||
const fontPick = document.getElementById('font-select');
|
||||
fontPick.value = root.classList.contains('nastaliq') ? 'nastaliq' : 'naskh';
|
||||
fontPick.onchange = () => {
|
||||
root.classList.toggle('nastaliq', fontPick.value === 'nastaliq');
|
||||
try { localStorage.setItem('divan-font', fontPick.value); } catch (e) {}
|
||||
// display settings: reading font, colour palette, light/dark/automatic; each choice is remembered
|
||||
const panel = document.querySelector('.display-menu');
|
||||
const store = (k, v) => { try { v ? localStorage.setItem(k, v) : localStorage.removeItem(k); } catch (e) {} };
|
||||
const marks = () => {
|
||||
const on = { font: root.classList.contains('nastaliq') ? 'nastaliq' : 'naskh', palette: root.dataset.palette ?? '', theme: root.dataset.theme ?? '' };
|
||||
panel.querySelectorAll('[data-font]').forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.font === on.font)));
|
||||
panel.querySelectorAll('[data-palette]').forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.palette === on.palette)));
|
||||
panel.querySelectorAll('[data-theme-pick]').forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.themePick === on.theme)));
|
||||
};
|
||||
panel.addEventListener('click', (e) => {
|
||||
const b = e.target.closest('button'); if (!b) return;
|
||||
if (b.dataset.font) { root.classList.toggle('nastaliq', b.dataset.font === 'nastaliq'); store('divan-font', b.dataset.font); }
|
||||
if (b.dataset.palette != null) { if (b.dataset.palette) root.dataset.palette = b.dataset.palette; else delete root.dataset.palette; store('divan-palette', b.dataset.palette); }
|
||||
if (b.dataset.themePick != null) { if (b.dataset.themePick) root.dataset.theme = b.dataset.themePick; else delete root.dataset.theme; store('divan-theme', b.dataset.themePick); }
|
||||
marks();
|
||||
});
|
||||
document.addEventListener('click', (e) => { if (panel.open && !panel.contains(e.target)) panel.open = false; }); // close when clicking elsewhere
|
||||
marks();
|
||||
// Urdu on-screen keyboard, Gboard/Windows Urdu layout
|
||||
const BASE = [[...'طصھدٹپتبجح'], [...'مورنلہاکی'], [...'قفےسشغع']];
|
||||
const SHIFT = [['ظ','ض','ذ','ڈ','ث','ّ','ۃ','ـ','چ','خ'], ['ژ','ز','ڑ','ں','ۂ','ء','آ','گ',''], ['','','ۓ','','ؤ','ئ','']];
|
||||
@ -404,11 +433,6 @@ import Icon from '../components/Icon.astro';
|
||||
if (!menu.hidden) hideMenu(); else if (!dict.hidden) closeDict();
|
||||
});
|
||||
|
||||
document.getElementById('theme-toggle').onclick = () => {
|
||||
const dark = root.dataset.theme ? root.dataset.theme === 'dark' : matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
root.dataset.theme = dark ? 'light' : 'dark';
|
||||
try { localStorage.setItem('divan-theme', root.dataset.theme); } catch (e) {}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -23,6 +23,20 @@
|
||||
--gold-subtle: #2a2418; --border: #9b7332; --brand: #d4553a; color-scheme: dark;
|
||||
--action: #7db3e8; --action-ink: #0d1520; --ok: #7cc47f; --danger: #f07a6e;
|
||||
}
|
||||
/* colour palettes (owner request): sepia (the default above) and four calmer colourful ones, each light and dark;
|
||||
chosen in the display settings (data-palette on <html>); dark follows the system or the reader's choice */
|
||||
:root[data-palette='teal'] { --bg: #f2f8f7; --paper: #fcfefe; --poem: #e2f0ee; --inner: #e9f3f1; --nested: #d4e8e4; --ink: #0f1f1d; --muted: #4a615e; --lapis: #0b4f6c; --gold: #2a9d8f; --gold-light: #b6dad4; --gold-subtle: #e4f2ef; --border: #8db8b1; --brand: #c8553d; --action: #0b4f6c; }
|
||||
@media (prefers-color-scheme: dark) { :root[data-palette='teal']:not([data-theme='light']) { --bg: #0f1716; --paper: #121c1b; --poem: #152220; --inner: #1a2927; --nested: #20322f; --ink: #dcebe8; --muted: #8fa8a4; --lapis: #e6f4f2; --gold: #3fb5a5; --gold-light: #23413c; --gold-subtle: #172b28; --border: #2f7d72; --brand: #e07a5f; --action: #5cc2b3; --action-ink: #0c1514; --ok: #7cc47f; --danger: #f07a6e; color-scheme: dark; } }
|
||||
:root[data-palette='teal'][data-theme='dark'] { --bg: #0f1716; --paper: #121c1b; --poem: #152220; --inner: #1a2927; --nested: #20322f; --ink: #dcebe8; --muted: #8fa8a4; --lapis: #e6f4f2; --gold: #3fb5a5; --gold-light: #23413c; --gold-subtle: #172b28; --border: #2f7d72; --brand: #e07a5f; --action: #5cc2b3; --action-ink: #0c1514; --ok: #7cc47f; --danger: #f07a6e; color-scheme: dark; }
|
||||
:root[data-palette='lapis'] { --bg: #f3f5fb; --paper: #fdfdff; --poem: #e6ebf8; --inner: #ecf0f9; --nested: #dae1f3; --ink: #131a2e; --muted: #4e5772; --lapis: #1d3b8b; --gold: #c49a2c; --gold-light: #d8d0ad; --gold-subtle: #eef0f8; --border: #98a6c8; --brand: #b83254; --action: #1d3b8b; }
|
||||
@media (prefers-color-scheme: dark) { :root[data-palette='lapis']:not([data-theme='light']) { --bg: #10131c; --paper: #131724; --poem: #171c2c; --inner: #1c2235; --nested: #232a40; --ink: #e1e5f2; --muted: #98a0bb; --lapis: #eef1fb; --gold: #d4b25a; --gold-light: #2c3350; --gold-subtle: #1b2034; --border: #4b5c9a; --brand: #e0607e; --action: #8ea9ee; --action-ink: #0e1220; --ok: #7cc47f; --danger: #f07a6e; color-scheme: dark; } }
|
||||
:root[data-palette='lapis'][data-theme='dark'] { --bg: #10131c; --paper: #131724; --poem: #171c2c; --inner: #1c2235; --nested: #232a40; --ink: #e1e5f2; --muted: #98a0bb; --lapis: #eef1fb; --gold: #d4b25a; --gold-light: #2c3350; --gold-subtle: #1b2034; --border: #4b5c9a; --brand: #e0607e; --action: #8ea9ee; --action-ink: #0e1220; --ok: #7cc47f; --danger: #f07a6e; color-scheme: dark; }
|
||||
:root[data-palette='green'] { --bg: #f3f7f0; --paper: #fdfefb; --poem: #e5eede; --inner: #ecf3e7; --nested: #d9e6cf; --ink: #151f0f; --muted: #54614a; --lapis: #1f5d3a; --gold: #a8872e; --gold-light: #d7cea2; --gold-subtle: #eff1e1; --border: #9db38c; --brand: #b5452f; --action: #1f5d3a; }
|
||||
@media (prefers-color-scheme: dark) { :root[data-palette='green']:not([data-theme='light']) { --bg: #111611; --paper: #141a13; --poem: #182017; --inner: #1d271b; --nested: #243022; --ink: #e0e8d8; --muted: #9aa88e; --lapis: #eef5e8; --gold: #c9a94e; --gold-light: #2c3a27; --gold-subtle: #1c251a; --border: #4f7a45; --brand: #e0705a; --action: #84c79a; --action-ink: #0d150f; --ok: #7cc47f; --danger: #f07a6e; color-scheme: dark; } }
|
||||
:root[data-palette='green'][data-theme='dark'] { --bg: #111611; --paper: #141a13; --poem: #182017; --inner: #1d271b; --nested: #243022; --ink: #e0e8d8; --muted: #9aa88e; --lapis: #eef5e8; --gold: #c9a94e; --gold-light: #2c3a27; --gold-subtle: #1c251a; --border: #4f7a45; --brand: #e0705a; --action: #84c79a; --action-ink: #0d150f; --ok: #7cc47f; --danger: #f07a6e; color-scheme: dark; }
|
||||
:root[data-palette='rose'] { --bg: #fbf4f6; --paper: #fffcfd; --poem: #f5e6ea; --inner: #f8edf0; --nested: #eedbe0; --ink: #29141a; --muted: #6c5057; --lapis: #6b2c5e; --gold: #c27c88; --gold-light: #e7c8ce; --gold-subtle: #f7e8ec; --border: #c8a1a9; --brand: #b0304a; --action: #6b2c5e; }
|
||||
@media (prefers-color-scheme: dark) { :root[data-palette='rose']:not([data-theme='light']) { --bg: #181214; --paper: #1c1518; --poem: #22191c; --inner: #291e22; --nested: #31252a; --ink: #efe0e4; --muted: #b39aa1; --lapis: #fbeef2; --gold: #d792a0; --gold-light: #3e2a30; --gold-subtle: #2a1d21; --border: #8a5563; --brand: #ef6f86; --action: #e3a3c3; --action-ink: #1d1216; --ok: #7cc47f; --danger: #f07a6e; color-scheme: dark; } }
|
||||
:root[data-palette='rose'][data-theme='dark'] { --bg: #181214; --paper: #1c1518; --poem: #22191c; --inner: #291e22; --nested: #31252a; --ink: #efe0e4; --muted: #b39aa1; --lapis: #fbeef2; --gold: #d792a0; --gold-light: #3e2a30; --gold-subtle: #2a1d21; --border: #8a5563; --brand: #ef6f86; --action: #e3a3c3; --action-ink: #1d1216; --ok: #7cc47f; --danger: #f07a6e; color-scheme: dark; }
|
||||
:root.nastaliq { --font: 'Noto Nastaliq Urdu', 'Noto Naskh Arabic', serif; --verse-lh: 2.7; }
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
@ -481,13 +495,23 @@ h1 + .muted { text-align: center; margin-top: 0; }
|
||||
.icon { flex: none; }
|
||||
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
||||
.icon-btn.small { width: 30px; height: 30px; }
|
||||
/* the font picker in the header: the icon and a list */
|
||||
.font-pick { display: inline-flex; align-items: center; gap: 4px; height: 36px; padding: 0 4px 0 10px; border: 1.5px solid var(--border); border-radius: 999px;
|
||||
background: var(--inner); color: var(--ink); cursor: pointer; }
|
||||
.font-pick:hover, .font-pick:focus-within { border-color: var(--brand); color: var(--brand); }
|
||||
.font-pick select { font: 400 .85rem/1.4 'Noto Naskh Arabic', serif; height: 28px; border: 0; background: transparent; color: inherit;
|
||||
cursor: pointer; padding: 0 2px; } /* Naskh at a fixed height whatever the reading font (Nastaliq would spill out) */
|
||||
.font-pick select:focus { outline: none; }
|
||||
/* display settings in the header: a button and its panel (font, palette swatches, light/dark/automatic) */
|
||||
.display-menu { position: relative; }
|
||||
.display-menu > summary { list-style: none; }
|
||||
.display-menu > summary::-webkit-details-marker { display: none; }
|
||||
.display-menu[open] > summary { border-color: var(--brand); color: var(--brand); }
|
||||
.display-panel { position: absolute; top: calc(100% + 8px); left: 0; z-index: 30; width: 260px; padding: 10px 14px; border: 1.5px solid var(--border);
|
||||
border-radius: 14px; background: var(--paper); box-shadow: 0 8px 24px rgb(0 0 0 / .18); font: 400 .9rem/1.8 'Noto Naskh Arabic', serif; color: var(--ink); }
|
||||
.display-panel p { margin: 6px 0 4px; color: var(--muted); font-size: .8rem; }
|
||||
.display-panel .seg { display: flex; gap: 4px; }
|
||||
.display-panel button { display: inline-flex; align-items: center; justify-content: center; gap: 4px; flex: 1; font: inherit; font-size: .85rem; padding: 2px 6px;
|
||||
border: 1.5px solid var(--border); border-radius: 10px; background: var(--inner); color: var(--ink); cursor: pointer; }
|
||||
.display-panel button[aria-pressed='true'] { border-color: var(--action); color: var(--action); font-weight: 700; }
|
||||
.display-panel .f-nastaliq { font-family: 'Noto Nastaliq Urdu', serif; line-height: 2.2; }
|
||||
.display-panel .swatches { display: grid; grid-template-columns: repeat(5, 1fr); gap: 4px; }
|
||||
.display-panel .swatches button { flex-direction: column; font-size: .7rem; padding: 4px 2px; }
|
||||
.display-panel .swatches span { width: 22px; height: 22px; border-radius: 50%; background: linear-gradient(135deg, var(--a) 50%, var(--b) 50%);
|
||||
box-shadow: 0 0 0 1px rgb(0 0 0 / .2); }
|
||||
.glyph-icon { position: relative; display: inline-flex; }
|
||||
.glyph-icon > span { position: absolute; inset: 0 0 4px 2px; display: flex; align-items: center; justify-content: center;
|
||||
font: 700 11px/1 'Noto Naskh Arabic', serif; }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user