diff --git a/public/_lab/lab.js b/public/_lab/lab.js index bc7eded..e8d852a 100644 --- a/public/_lab/lab.js +++ b/public/_lab/lab.js @@ -20,9 +20,10 @@ function apply(patch) { if (state.radius) root.dataset.radius = state.radius; if (state.font) root.dataset.font = state.font; - root.style.setProperty("--theme-h", String(state.h)); - root.style.setProperty("--theme-s", String(state.s)); - root.style.setProperty("--theme-l", String(state.l)); + const labAcc = state[state.mode] || state.light || { h: 210, s: 90, l: 40 }; + root.style.setProperty("--theme-h", String(labAcc.h)); + root.style.setProperty("--theme-s", String(labAcc.s)); + root.style.setProperty("--theme-l", String(labAcc.l)); ensureThemeFontLoaded(state.font); syncButtons(); diff --git a/public/assets/misc/github_logo.webp b/public/assets/misc/github_logo.webp new file mode 100644 index 0000000..2edbcd7 Binary files /dev/null and b/public/assets/misc/github_logo.webp differ diff --git a/public/assets/shared/icons/whatsapp.svg b/public/assets/shared/icons/whatsapp.svg new file mode 100644 index 0000000..ae5ca23 --- /dev/null +++ b/public/assets/shared/icons/whatsapp.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/css/main.css b/public/css/main.css index 5fd6634..630fc75 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -3383,7 +3383,7 @@ html[data-theme="light"] .hero__portrait-shell { height: auto; object-fit: cover; object-position: center; - border: 1px solid color-mix(in srgb, var(--line) 84%, var(--text) 16%); + border: 1px solid var(--line); border-radius: calc(var(--radius-card) - 4px); box-sizing: border-box; } @@ -4400,6 +4400,10 @@ html[data-radius="strong"] .skill-task-card__solution { --contact-brand: #0077ff; } +.contact-card--brand-whatsapp { + --contact-brand: #25D366; +} + .contact-card--brand-ya { --contact-brand: #f8604a; } diff --git a/public/en/cases/ai-assistant-figma/index.html b/public/en/cases/ai-assistant-figma/index.html index f97eefb..73d7c90 100644 --- a/public/en/cases/ai-assistant-figma/index.html +++ b/public/en/cases/ai-assistant-figma/index.html @@ -362,7 +362,7 @@

Contact

diff --git a/public/en/cases/ai-platform-primo-rpa-2/index.html b/public/en/cases/ai-platform-primo-rpa-2/index.html index 941009e..8732a1f 100644 --- a/public/en/cases/ai-platform-primo-rpa-2/index.html +++ b/public/en/cases/ai-platform-primo-rpa-2/index.html @@ -1008,7 +1008,7 @@

Contact

diff --git a/public/en/cases/ai-platform-primo-rpa-v2/index.html b/public/en/cases/ai-platform-primo-rpa-v2/index.html index 081f329..11330d2 100644 --- a/public/en/cases/ai-platform-primo-rpa-v2/index.html +++ b/public/en/cases/ai-platform-primo-rpa-v2/index.html @@ -1138,7 +1138,7 @@

Contact

diff --git a/public/en/cases/ai-platform-primo-rpa/index.html b/public/en/cases/ai-platform-primo-rpa/index.html index 701e2e5..c373262 100644 --- a/public/en/cases/ai-platform-primo-rpa/index.html +++ b/public/en/cases/ai-platform-primo-rpa/index.html @@ -1172,7 +1172,7 @@

Contact

diff --git a/public/en/cases/card-columns-obsidian/index.html b/public/en/cases/card-columns-obsidian/index.html index 2b73984..f09b3bb 100644 --- a/public/en/cases/card-columns-obsidian/index.html +++ b/public/en/cases/card-columns-obsidian/index.html @@ -758,7 +758,7 @@

Contact

diff --git a/public/en/cases/lunacy/index.html b/public/en/cases/lunacy/index.html index 68ba1c0..a601eae 100644 --- a/public/en/cases/lunacy/index.html +++ b/public/en/cases/lunacy/index.html @@ -960,7 +960,7 @@

Contact

diff --git a/public/en/cases/manu-game-maker/index.html b/public/en/cases/manu-game-maker/index.html index 2f167f3..425ddf6 100644 --- a/public/en/cases/manu-game-maker/index.html +++ b/public/en/cases/manu-game-maker/index.html @@ -702,7 +702,7 @@

Contact

diff --git a/public/en/cases/mobile-clinic-app/index.html b/public/en/cases/mobile-clinic-app/index.html index 4bc76e6..f8c7dd0 100644 --- a/public/en/cases/mobile-clinic-app/index.html +++ b/public/en/cases/mobile-clinic-app/index.html @@ -702,7 +702,7 @@

Contact

diff --git a/public/en/cases/obsidian-plugin-columns-view/index.html b/public/en/cases/obsidian-plugin-columns-view/index.html index 618e3ba..986a0ab 100644 --- a/public/en/cases/obsidian-plugin-columns-view/index.html +++ b/public/en/cases/obsidian-plugin-columns-view/index.html @@ -736,7 +736,7 @@

Contact

diff --git a/public/en/cases/orc-automation-panel/index.html b/public/en/cases/orc-automation-panel/index.html index fb64eea..a1ca989 100644 --- a/public/en/cases/orc-automation-panel/index.html +++ b/public/en/cases/orc-automation-panel/index.html @@ -841,7 +841,7 @@

Contact

diff --git a/public/en/cases/site-with-ai-ide/index.html b/public/en/cases/site-with-ai-ide/index.html index 2af9750..50be3e3 100644 --- a/public/en/cases/site-with-ai-ide/index.html +++ b/public/en/cases/site-with-ai-ide/index.html @@ -362,7 +362,7 @@

Contact

diff --git a/public/en/cases/task-distribution-concept/index.html b/public/en/cases/task-distribution-concept/index.html index bc4aa71..3bffe9e 100644 --- a/public/en/cases/task-distribution-concept/index.html +++ b/public/en/cases/task-distribution-concept/index.html @@ -702,7 +702,7 @@

Contact

diff --git a/public/en/cases/task-tracker/index.html b/public/en/cases/task-tracker/index.html index 7bcfc2c..279b554 100644 --- a/public/en/cases/task-tracker/index.html +++ b/public/en/cases/task-tracker/index.html @@ -1170,7 +1170,7 @@

Contact

diff --git a/public/en/cases/template-elements-demo/index.html b/public/en/cases/template-elements-demo/index.html index c3bffe1..0565238 100644 --- a/public/en/cases/template-elements-demo/index.html +++ b/public/en/cases/template-elements-demo/index.html @@ -1095,7 +1095,7 @@

Contact

diff --git a/public/en/index.html b/public/en/index.html index c33064e..0945068 100644 --- a/public/en/index.html +++ b/public/en/index.html @@ -1265,6 +1265,36 @@ +
  • + + +
    +

    + GitHub + +

    +

    + My projects and code experiments. +

    +
    +
    +
  • Contact diff --git a/public/index.html b/public/index.html index be3b211..dd1dda9 100644 --- a/public/index.html +++ b/public/index.html @@ -1287,6 +1287,36 @@
  • +
  • + + +
    +

    + GitHub + +

    +

    + Мои проекты и эксперименты с кодом. +

    +
    +
    +
  • + btn.addEventListener("click", () => { + if (!panel.hidden) { + syncHueSlider(); + drawSLPlane(canvas, acc().h); + updateKnob(knob, acc().s, acc().l); + } + }), + ); if (meshBgBtns.length >= 2) bindOptionButtons(meshBgBtns, "data-mesh-bg-option", MESH_BG_IDS, "meshBg"); function setPanelOpen(open) { @@ -84,8 +98,8 @@ export function initThemeControls() { btn.setAttribute("aria-label", open ? "Закрыть настройки стиля" : "Настройки стиля"); }); if (open) { - drawSLPlane(canvas, state.h); - updateKnob(knob, state.s, state.l); + drawSLPlane(canvas, acc().h); + updateKnob(knob, acc().s, acc().l); syncAriaRadioGroup(themeModeBtns, "data-theme-mode", state.mode); syncAriaRadioGroup(fontBtns, "data-font-option", state.font); if (meshBgBtns.length >= 2) syncAriaRadioGroup(meshBgBtns, "data-mesh-bg-option", state.meshBg); @@ -95,6 +109,11 @@ export function initThemeControls() { subscribePrefersColorSchemeChange(() => { if (state.mode !== "system") return; applyThemeState(state); + if (!panel.hidden) { + syncHueSlider(); + drawSLPlane(canvas, acc().h); + updateKnob(knob, acc().s, acc().l); + } }); panelBtns.forEach((btn) => { @@ -143,9 +162,10 @@ export function initThemeControls() { if (r.width <= 0 || r.height <= 0) return; const x = clampNum(((clientX - r.left) / r.width) * 100, 0, 100, 0) / 100; const y = clampNum(((clientY - r.top) / r.height) * 100, 0, 100, 0) / 100; - state.s = Math.round(PICK.sMin + x * S_RANGE); - state.l = Math.round(PICK.lMin + (1 - y) * L_RANGE); - updateKnob(knob, state.s, state.l); + const a = acc(); + a.s = Math.round(PICK.sMin + x * S_RANGE); + a.l = Math.round(PICK.lMin + (1 - y) * L_RANGE); + updateKnob(knob, a.s, a.l); persistAndApply(); } @@ -168,10 +188,11 @@ export function initThemeControls() { }); hueSlider.addEventListener("input", () => { - state.h = clampNum(hueSlider.value, 0, 360, state.h); + const a = acc(); + a.h = clampNum(hueSlider.value, 0, 360, a.h); syncHueSlider(); - drawSLPlane(canvas, state.h); - updateKnob(knob, state.s, state.l); + drawSLPlane(canvas, a.h); + updateKnob(knob, a.s, a.l); persistAndApply(); }); } diff --git a/public/js/modules/theme/theme-core.js b/public/js/modules/theme/theme-core.js index a16e4d3..63a5b4a 100644 --- a/public/js/modules/theme/theme-core.js +++ b/public/js/modules/theme/theme-core.js @@ -27,7 +27,14 @@ export const S_RANGE = PICK.sMax - PICK.sMin; export const L_RANGE = PICK.lMax - PICK.lMin; export function defaultThemeState() { - return { mode: "system", h: 210, s: 90, l: 40, radius: "medium", font: "geologica", meshBg: "on" }; + return { + mode: "system", + light: { h: 210, s: 90, l: 40 }, + dark: { h: 210, s: 90, l: 40 }, + radius: "medium", + font: "geologica", + meshBg: "on", + }; } export function effectiveThemeMode(state) { @@ -35,23 +42,53 @@ export function effectiveThemeMode(state) { return state.mode; } +export function activeAccent(state) { + const key = effectiveThemeMode(state); + return state[key] || state.light || { h: 210, s: 90, l: 40 }; +} + +function parseAccent(obj, base) { + if (!obj) return { ...base }; + return { + h: clampNum(obj.h ?? obj.hue, 0, 360, base.h), + s: clampNum(obj.s ?? obj.sat, PICK.sMin, PICK.sMax, base.s), + l: clampNum(obj.l ?? obj.light, PICK.lMin, PICK.lMax, base.l), + }; +} + export function readThemeState() { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return defaultThemeState(); const o = JSON.parse(raw); const base = defaultThemeState(); - const hRaw = o.h ?? o.hue; - const h = clampNum(hRaw, 0, 360, base.h); - const s = clampNum(o.s ?? o.sat, PICK.sMin, PICK.sMax, base.s); - const lRaw = o.l ?? o.light; - const l = clampNum(lRaw, PICK.lMin, PICK.lMax, base.l); const mode = o.mode === "light" || o.mode === "dark" || o.mode === "system" ? o.mode : base.mode; const radius = RADIUS_IDS.includes(o.radius) ? o.radius : base.radius; const font = normalizeFontId(o.font ?? base.font); const meshBg = MESH_BG_IDS.includes(o.meshBg) ? o.meshBg : base.meshBg; - return { mode, h, s, l, radius, font, meshBg }; + + let light = base.light; + let dark = base.dark; + if (o.light || o.dark) { + light = parseAccent(o.light, base.light); + dark = parseAccent(o.dark, base.dark); + } else if ( + o.h !== undefined || + o.hue !== undefined || + o.s !== undefined || + o.sat !== undefined || + o.l !== undefined || + o.light !== undefined + ) { + // legacy: единый акцент → копируем в обе темы + const h = clampNum(o.h ?? o.hue, 0, 360, base.light.h); + const s = clampNum(o.s ?? o.sat, PICK.sMin, PICK.sMax, base.light.s); + const l = clampNum(o.l ?? o.light, PICK.lMin, PICK.lMax, base.light.l); + light = { h, s, l }; + dark = { h, s, l }; + } + return { mode, light, dark, radius, font, meshBg }; } catch { return defaultThemeState(); } @@ -62,9 +99,8 @@ export function writeThemeState(state) { STORAGE_KEY, JSON.stringify({ mode: state.mode, - h: state.h, - s: state.s, - l: state.l, + light: state.light, + dark: state.dark, radius: state.radius, font: state.font, meshBg: state.meshBg, @@ -110,14 +146,16 @@ export function relativeLuminance(rgb) { export function applyThemeState(state) { const root = document.documentElement; - root.dataset.theme = effectiveThemeMode(state); + const mode = effectiveThemeMode(state); + root.dataset.theme = mode; root.dataset.radius = state.radius; root.dataset.font = state.font; root.dataset.meshBg = state.meshBg === "off" ? "off" : "on"; - root.style.setProperty("--theme-h", String(state.h)); - root.style.setProperty("--theme-s", String(state.s)); - root.style.setProperty("--theme-l", String(state.l)); - const rgb = hslToRgb(state.h, state.s, state.l); + const acc = activeAccent(state); + root.style.setProperty("--theme-h", String(acc.h)); + root.style.setProperty("--theme-s", String(acc.s)); + root.style.setProperty("--theme-l", String(acc.l)); + const rgb = hslToRgb(acc.h, acc.s, acc.l); const lum = relativeLuminance(rgb); root.style.setProperty("--on-accent", lum > 0.58 ? "hsl(220 14% 12%)" : "hsl(0 0% 98%)"); window.dispatchEvent(new CustomEvent("site-theme-applied", { bubbles: true })); diff --git a/src/partials/contact-section.en.html b/src/partials/contact-section.en.html index 163f0f3..58f581a 100644 --- a/src/partials/contact-section.en.html +++ b/src/partials/contact-section.en.html @@ -3,7 +3,7 @@

    Contact

    diff --git a/src/partials/mini-projects-section.en.html b/src/partials/mini-projects-section.en.html index b9e3a3b..adb4716 100644 --- a/src/partials/mini-projects-section.en.html +++ b/src/partials/mini-projects-section.en.html @@ -108,6 +108,36 @@
  • +
  • + + +
    +

    + GitHub + +

    +

    + My projects and code experiments. +

    +
    +
    +
  • +
  • + + +
    +

    + GitHub + +

    +

    + Мои проекты и эксперименты с кодом. +

    +
    +
    +