Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/marketing/content/site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export const CHROME: Record<
| 'footer.license'
| 'lang.switch'
| 'theme.toggle'
| 'menu.open'
| 'menu.close'
| 'skipToContent'
| 'breadcrumb.home',
Localized
Expand Down Expand Up @@ -77,6 +79,8 @@ export const CHROME: Record<
'footer.license': { en: 'MIT licensed', ar: 'رخصة MIT' },
'lang.switch': { en: 'العربية', ar: 'English' },
'theme.toggle': { en: 'Switch light/dark theme', ar: 'بدِّل بين الوضع الفاتح والداكن' },
'menu.open': { en: 'Open menu', ar: 'افتح القائمة' },
'menu.close': { en: 'Close menu', ar: 'أغلق القائمة' },
skipToContent: { en: 'Skip to content', ar: 'تخطَّ إلى المحتوى' },
'breadcrumb.home': { en: 'Home', ar: 'الرئيسية' },
}
Expand Down
2 changes: 1 addition & 1 deletion src/marketing/layout/MarketingLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { SiteHeader } from './SiteHeader'
* content is simply visible — never hidden.
*/
const ENHANCE_SCRIPT =
"(function(){var d=document.documentElement;if(d.getAttribute('data-mk-enhanced'))return;d.setAttribute('data-mk-enhanced','1');try{var s=localStorage.getItem('scripto:mk-theme');if(s==='light')d.classList.remove('dark');else if(s==='dark')d.classList.add('dark')}catch(e){}d.classList.add('js');document.addEventListener('click',function(ev){var t=ev.target;var c=t&&t.closest?t.closest('[data-copy]'):null;if(c){var box=c.closest('.mk-codebox');var pre=box&&box.querySelector('pre');if(pre&&navigator.clipboard){navigator.clipboard.writeText(pre.innerText).then(function(){var o=c.textContent;c.textContent='Copied!';setTimeout(function(){c.textContent=o},1400)})}return}var b=t&&t.closest?t.closest('[data-theme-toggle]'):null;if(!b)return;var k=d.classList.toggle('dark');try{localStorage.setItem('scripto:mk-theme',k?'dark':'light')}catch(e){}});var o='IntersectionObserver'in window?new IntersectionObserver(function(t){t.forEach(function(n){n.isIntersecting&&(n.target.classList.add('in'),o.unobserve(n.target))})},{rootMargin:'0px 0px -8% 0px',threshold:0.08}):null;function scan(){var e=document.querySelectorAll('.mk-reveal:not([data-obs])');for(var i=0;i<e.length;i++){e[i].setAttribute('data-obs','1');if(o)o.observe(e[i]);else e[i].classList.add('in')}var v=document.querySelector('video[data-hero-video]');if(v&&!v.getAttribute('data-obs')){v.setAttribute('data-obs','1');var ok=function(){d.classList.add('has-vid')};if(v.readyState>=2)ok();else v.addEventListener('loadeddata',ok,{once:true})}}if(document.readyState==='loading')addEventListener('DOMContentLoaded',scan);scan();addEventListener('load',scan);setTimeout(scan,400);setTimeout(scan,1600)})();"
"(function(){var d=document.documentElement;if(d.getAttribute('data-mk-enhanced'))return;d.setAttribute('data-mk-enhanced','1');try{var s=localStorage.getItem('scripto:mk-theme');if(s==='light')d.classList.remove('dark');else if(s==='dark')d.classList.add('dark')}catch(e){}d.classList.add('js');document.addEventListener('click',function(ev){var t=ev.target;var c=t&&t.closest?t.closest('[data-copy]'):null;if(c){var box=c.closest('.mk-codebox');var pre=box&&box.querySelector('pre');if(pre&&navigator.clipboard){navigator.clipboard.writeText(pre.innerText).then(function(){var o=c.textContent;c.textContent='Copied!';setTimeout(function(){c.textContent=o},1400)})}return}var b=t&&t.closest?t.closest('[data-theme-toggle]'):null;if(!b)return;var k=d.classList.toggle('dark');try{localStorage.setItem('scripto:mk-theme',k?'dark':'light')}catch(e){}});function mkMenu(){return document.querySelector('details.mk-menu')}document.addEventListener('click',function(ev){var t=ev.target;var m=mkMenu();if(!m||!m.open||!t||!t.closest)return;if(t.closest('.mk-menu-panel a')){m.open=false;return}if(!t.closest('details.mk-menu'))m.open=false});document.addEventListener('keydown',function(ev){if(ev.key!=='Escape')return;var m=mkMenu();if(m&&m.open){m.open=false;var s=m.querySelector('summary');if(s)s.focus()}});addEventListener('resize',function(){if(innerWidth>=900){var m=mkMenu();if(m&&m.open)m.open=false}});document.addEventListener('toggle',function(ev){var m=ev.target;if(!m||!m.classList||!m.classList.contains('mk-menu'))return;var s=m.querySelector('summary');if(s){s.setAttribute('aria-expanded',m.open?'true':'false');var lb=s.getAttribute(m.open?'data-label-close':'data-label-open');if(lb)s.setAttribute('aria-label',lb)}d.classList.toggle('mk-menu-open',m.open)},true);var o='IntersectionObserver'in window?new IntersectionObserver(function(t){t.forEach(function(n){n.isIntersecting&&(n.target.classList.add('in'),o.unobserve(n.target))})},{rootMargin:'0px 0px -8% 0px',threshold:0.08}):null;function scan(){var e=document.querySelectorAll('.mk-reveal:not([data-obs])');for(var i=0;i<e.length;i++){e[i].setAttribute('data-obs','1');if(o)o.observe(e[i]);else e[i].classList.add('in')}var v=document.querySelector('video[data-hero-video]');if(v&&!v.getAttribute('data-obs')){v.setAttribute('data-obs','1');var ok=function(){d.classList.add('has-vid')};if(v.readyState>=2)ok();else v.addEventListener('loadeddata',ok,{once:true})}}if(document.readyState==='loading')addEventListener('DOMContentLoaded',scan);scan();addEventListener('load',scan);setTimeout(scan,400);setTimeout(scan,1600)})();"

interface MarketingLayoutProps {
lang: MarketingLang
Expand Down
137 changes: 98 additions & 39 deletions src/marketing/layout/SiteHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,57 @@ const NAV_ITEMS: { to: string; key: 'nav.features' | 'nav.templates' | 'nav.skin
{ to: '/blog', key: 'nav.blog' },
]

function ThemeToggle({ lang }: { lang: MarketingLang }) {
return (
<button
type="button"
className="mk-btn mk-btn-ghost mk-btn-md mk-theme-toggle"
data-theme-toggle
aria-label={chrome('theme.toggle', lang)}
title={chrome('theme.toggle', lang)}
>
{/* sun shown in dark mode (switch to light), moon in light mode */}
<svg className="tt-sun" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
<circle cx="12" cy="12" r="4" />
<path d="M12 2v2m0 16v2M4.9 4.9l1.4 1.4m11.4 11.4 1.4 1.4M2 12h2m16 0h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" />
</svg>
<svg className="tt-moon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z" />
</svg>
</button>
)
}

function GithubLink({ lang }: { lang: MarketingLang }) {
return (
<a
href={GITHUB_URL}
className="mk-btn mk-btn-ghost mk-btn-md"
target="_blank"
rel="noopener"
aria-label={chrome('nav.github', lang)}
>
<svg viewBox="0 0 16 16" width="18" height="18" fill="currentColor" aria-hidden="true">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z" />
</svg>
<span className="mk-gh-label">{chrome('nav.github', lang)}</span>
</a>
)
}

function LangSwitch({ lang, altPath }: { lang: MarketingLang; altPath: string }) {
return (
<a
href={altPath}
className="mk-btn mk-btn-ghost mk-btn-md"
lang={lang === 'ar' ? 'en' : 'ar'}
dir={lang === 'ar' ? 'ltr' : 'rtl'}
>
{chrome('lang.switch', lang)}
</a>
)
}

export function SiteHeader({ lang, altPath }: SiteHeaderProps) {
const brand = lang === 'ar' ? SITE_NAME_AR : SITE_NAME

Expand All @@ -25,58 +76,66 @@ export function SiteHeader({ lang, altPath }: SiteHeaderProps) {
<div className="mk-container mk-header-inner">
<a href={localePath(lang, '/')} className="mk-logo" aria-label={`${brand} — ${chrome('breadcrumb.home', lang)}`}>
<img src="/logo-192.png" alt="" width={28} height={28} />
<span>{brand}</span>
<span className="mk-logo-word">{brand}</span>
</a>

<nav className="mk-nav" aria-label="Primary">
{NAV_ITEMS.map((item) => (
<a key={item.to} href={item.to}>
{chrome(item.key, lang)}
</a>
))}
</nav>

{/* Desktop action cluster (≥900px) */}
<div className="mk-header-actions">
<button
type="button"
className="mk-btn mk-btn-ghost mk-btn-md mk-theme-toggle"
data-theme-toggle
aria-label={chrome('theme.toggle', lang)}
title={chrome('theme.toggle', lang)}
>
{/* sun shown in dark mode (switch to light), moon in light mode */}
<svg className="tt-sun" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
<circle cx="12" cy="12" r="4" />
<path d="M12 2v2m0 16v2M4.9 4.9l1.4 1.4m11.4 11.4 1.4 1.4M2 12h2m16 0h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" />
</svg>
<svg className="tt-moon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z" />
</svg>
</button>
{altPath ? (
<a
href={altPath}
className="mk-btn mk-btn-ghost mk-btn-md"
lang={lang === 'ar' ? 'en' : 'ar'}
dir={lang === 'ar' ? 'ltr' : 'rtl'}
>
{chrome('lang.switch', lang)}
</a>
) : null}
<a
href={GITHUB_URL}
className="mk-btn mk-btn-ghost mk-btn-md"
target="_blank"
rel="noopener"
aria-label={chrome('nav.github', lang)}
>
<svg viewBox="0 0 16 16" width="18" height="18" fill="currentColor" aria-hidden="true">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z" />
</svg>
<span className="mk-gh-label">{chrome('nav.github', lang)}</span>
</a>
<ThemeToggle lang={lang} />
{altPath ? <LangSwitch lang={lang} altPath={altPath} /> : null}
<GithubLink lang={lang} />
<a href={APP_PATH} className="mk-btn mk-btn-primary mk-btn-md">
{chrome('openApp', lang)}
</a>
</div>

{/* Compact cluster (<900px): primary CTA stays 1-tap; nav + secondary actions collapse into the menu.
zero-JS: native <details> opens the menu, ENHANCE_SCRIPT adds the niceties (never React state). */}
<div className="mk-header-compact">
<a href={APP_PATH} className="mk-btn mk-btn-primary mk-btn-sm mk-cta-compact">
{chrome('openApp', lang)}
</a>
<details className="mk-menu">
<summary
className="mk-menu-toggle"
aria-controls="mk-menu-panel"
aria-expanded={false}
aria-label={chrome('menu.open', lang)}
data-label-open={chrome('menu.open', lang)}
data-label-close={chrome('menu.close', lang)}
>
<svg className="mk-icon mk-icon-open" viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
<path d="M3 6h18M3 12h18M3 18h18" />
</svg>
<svg className="mk-icon mk-icon-close" viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
<path d="M6 6l12 12M18 6L6 18" />
</svg>
</summary>
<div id="mk-menu-panel" className="mk-menu-panel">
<nav className="mk-menu-nav" aria-label="Primary">
{NAV_ITEMS.map((item) => (
<a key={item.to} href={item.to}>
{chrome(item.key, lang)}
</a>
))}
</nav>
<hr className="mk-menu-divider" />
<div className="mk-menu-actions">
<GithubLink lang={lang} />
{altPath ? <LangSwitch lang={lang} altPath={altPath} /> : null}
<ThemeToggle lang={lang} />
</div>
</div>
</details>
</div>
</div>
</header>
)
Expand Down
168 changes: 166 additions & 2 deletions src/marketing/marketing.css
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,26 @@ html.dark {
}

.mk-header-inner {
position: relative;
display: flex;
align-items: center;
gap: 1.25rem;
gap: 0.75rem;
block-size: 4rem;
}

@media (min-width: 900px) {
.mk-header-inner {
gap: 1.25rem;
}
}

/* Hide the wordmark on the very narrowest screens; keep the logo icon. */
@media (max-width: 359px) {
.mk-logo-word {
display: none;
}
}

.mk-logo {
display: inline-flex;
align-items: center;
Expand Down Expand Up @@ -244,19 +258,164 @@ html.dark {
background: var(--mk-surface-3);
}

/* Desktop action cluster — collapses into the menu below the nav breakpoint. */
.mk-header-actions {
display: flex;
display: none;
align-items: center;
gap: 0.625rem;
margin-inline-start: auto;
}

@media (min-width: 900px) {
.mk-header-actions {
display: flex;
}
}

/* Compact cluster (CTA + hamburger) shown below the nav breakpoint. */
.mk-header-compact {
display: flex;
align-items: center;
gap: 0.5rem;
margin-inline-start: auto;
}

@media (min-width: 900px) {
.mk-header-compact {
display: none;
}
}

@media (max-width: 640px) {
.mk-gh-label {
display: none;
}
}

/* ---------- mobile menu (zero-JS: native <details>) ---------- */

.mk-menu {
display: inline-flex;
}

.mk-menu-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
min-inline-size: 2.75rem;
min-block-size: 2.75rem;
border-radius: 0.75rem;
color: var(--mk-fg);
background: var(--mk-chip-bg);
border: 1px solid var(--mk-border-strong);
cursor: pointer;
list-style: none;
transition: background-color 0.15s ease, border-color 0.15s ease;
}

.mk-menu-toggle::-webkit-details-marker {
display: none;
}

.mk-menu-toggle:hover {
border-color: var(--mk-accent-border);
background: var(--mk-surface-3);
}

.mk-menu-toggle:focus-visible {
outline: 2px solid var(--mk-accent-fg);
outline-offset: 2px;
}

/* hamburger ↔ X reflects the open state */
.mk-icon-close {
display: none;
}

.mk-menu[open] .mk-icon-open {
display: none;
}

.mk-menu[open] .mk-icon-close {
display: block;
}

.mk-menu-panel {
position: absolute;
inset-block-start: calc(100% + 0.5rem);
inset-inline: 0;
z-index: 60;
display: flex;
flex-direction: column;
max-block-size: calc(100dvh - 5rem);
overflow-y: auto;
padding: 0.75rem;
border: 1px solid var(--mk-border);
border-radius: 1rem;
background: var(--mk-bg);
box-shadow: var(--mk-card-shadow);
}

.mk-menu[open] .mk-menu-panel {
animation: mk-menu-in 0.16s cubic-bezier(0.16, 1, 0.3, 1);
}

@keyframes mk-menu-in {
from {
opacity: 0;
transform: translateY(-0.5rem);
}
}

.mk-menu-nav {
display: flex;
flex-direction: column;
gap: 0.125rem;
}

.mk-menu-nav a {
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
font-size: 1rem;
font-weight: 550;
color: var(--mk-fg);
transition: background-color 0.15s ease;
}

.mk-menu-nav a:hover {
background: var(--mk-surface-3);
}

.mk-menu-divider {
block-size: 1px;
border: 0;
margin: 0.625rem 0;
background: var(--mk-border);
}

.mk-menu-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem;
}

/* Always label GitHub inside the menu, even under the 640px cutoff. */
.mk-menu-actions .mk-gh-label {
display: inline;
}

/* Lock background scroll while the menu is open (ENHANCE_SCRIPT toggles the class). */
html.mk-menu-open body {
overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
.mk-menu[open] .mk-menu-panel {
animation: none;
}
}

.mk-skip-link {
position: absolute;
inset-inline-start: 1rem;
Expand Down Expand Up @@ -315,6 +474,11 @@ html:not(.dark) .mk-theme-toggle .tt-sun {
font-size: 0.9375rem;
}

.mk-btn-sm {
padding: 0.5rem 0.875rem;
font-size: 0.875rem;
}

.mk-btn-primary {
color: white;
background: linear-gradient(135deg, hsl(239 84% 63%), hsl(266 80% 60%));
Expand Down
Loading