diff --git a/ghostati-docs.html b/ghostati-docs.html index ade58c6..3ef00c0 100644 --- a/ghostati-docs.html +++ b/ghostati-docs.html @@ -5,11 +5,44 @@ Ghòstati - Guida per Sviluppatori + - + + - +
@@ -31,12 +64,23 @@
-

Documentazione

-
Sviluppa il tuo Ghostyle
- +
+

Documentazione

+
Sviluppa il tuo Ghostyle
+
+ +
-

Architettura ad Alto Livello

+

Architettura ad Alto Livello

Il sistema "Ghòstati" utilizza un'architettura a plugin modulari pensata per separare il nucleo di tracciamento facciale dagli effetti visivi (i "Ghostyles").

@@ -56,7 +100,7 @@

Architettura ad Alto Livello

-

Template Didattico

+

Template Didattico

Un plugin Ghòstyle per funzionare deve unicamente esportare un paio di funzioni specifiche (gli "Event Hooks"). Questo snippet di codice rappresenta lo scheletro di base per cominciare a sviluppare.

@@ -109,7 +153,7 @@

Template Didattico

-

Spiegazione dei Blocchi

+

Spiegazione dei Blocchi

1. Metadati Commentati @@ -149,7 +193,7 @@

Spiegazione dei Blocchi

-

Modalità Diagnostica (Test CV Dazzle)

+

Modalità Diagnostica (Test CV Dazzle)

Ghòstati non si limita ad applicare il trucco AR in modo cosmetico, ma include uno strumento di Diagnostica e Analisi delle Vulnerabilità progettato per valutare la reale efficacia del Ghostyle nel neutralizzare gli algoritmi di Facial Recognition (CV Dazzle). diff --git a/ghostati.html b/ghostati.html index f555174..9f25b34 100644 --- a/ghostati.html +++ b/ghostati.html @@ -22,13 +22,35 @@ - + + + diff --git a/images/ghostati-hero.png b/images/ghostati-hero.png index 24e738c..40ba35b 100644 Binary files a/images/ghostati-hero.png and b/images/ghostati-hero.png differ diff --git a/index.html b/index.html index 2b5066e..fd85f0a 100644 --- a/index.html +++ b/index.html @@ -45,8 +45,101 @@ - + + + + diff --git a/scripts/ghostati-mobile-ui.js b/scripts/ghostati-mobile-ui.js index d4ddb8b..bebdd7c 100644 --- a/scripts/ghostati-mobile-ui.js +++ b/scripts/ghostati-mobile-ui.js @@ -60,6 +60,11 @@ document.addEventListener('DOMContentLoaded', () => { return el.closest('.scrollable') || el.closest('#settingsDrawer'); }; + /* [SYSTEM API: document.addEventListener('touchstart'/'touchend')] + * Funzionamento: API nativa per intercettare il tocco delle dita sui display touch. + * Parametri: 'touchstart', callback(e), { passive: true } (migliora le performance dicendo al browser che non useremo preventDefault). + * Feature: Registra l'inizio e la fine di uno swipe verticale per attivare la pulizia dei log e dell'overlay (gesture UI). + */ document.addEventListener('touchstart', (e) => { if (isScrollableElement(e.target)) return; touchStartY = e.changedTouches[0].screenY; @@ -71,7 +76,11 @@ document.addEventListener('DOMContentLoaded', () => { handleGesture(); }, { passive: true }); - // Scroll wheel for desktop + /* [SYSTEM API: document.addEventListener('wheel')] + * Funzionamento: Intercetta la rotellina del mouse o il trackpad su desktop. + * Parametri: 'wheel', callback(e), { passive: true }. + * Feature: Replica il comportamento dello swipe (clear overlay e log) anche per gli utenti desktop tramite wheel/scroll. + */ document.addEventListener('wheel', (e) => { if (isScrollableElement(e.target)) return; // Debounce or just trigger on any significant scroll diff --git a/scripts/ghostati.js b/scripts/ghostati.js index 4ec5b8b..7f5dc26 100644 --- a/scripts/ghostati.js +++ b/scripts/ghostati.js @@ -95,6 +95,11 @@ let lastCompositedCanvas = null; function loadDb() { try { + /* [SYSTEM API: localStorage] + * Funzionamento: Storage persistente chiave-valore sincrono nativo del browser. + * Parametri: getItem(key) per leggere stringhe. + * Feature: Usato per caricare il DB locale dei volti salvati in precedenza. + */ const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return { nextId: 0, faces: [] }; const parsed = JSON.parse(raw); @@ -108,6 +113,11 @@ function loadDb() { } function persistDb() { + /* [SYSTEM API: localStorage] + * Funzionamento: Scrittura persistente sincrona nel browser. + * Parametri: setItem(key, value) + * Feature: Usato per salvare il DB aggiornato con le nuove feature biometriche, serializzato in JSON. + */ localStorage.setItem(STORAGE_KEY, JSON.stringify(db)); renderDbStats(); } @@ -609,6 +619,11 @@ function effectLoop(ts = 0) { lastEffectRun = ts; runEffectPass(); } + /* [SYSTEM API: requestAnimationFrame] + * Funzionamento: Richiede al browser di chiamare una funzione prima del prossimo repaint. + * Parametri: callback(timestamp) + * Feature: Crea un loop di rendering efficiente per le maschere AR, limitando i calcoli superflui e sincronizzandosi col display. + */ effectLoopHandle = requestAnimationFrame(effectLoop); } @@ -618,6 +633,11 @@ function startEffectLoop() { } function stopEffectLoop() { + /* [SYSTEM API: cancelAnimationFrame] + * Funzionamento: Annulla una richiesta di animazione precedentemente schedulata. + * Parametri: handle (ID numerico restituito da rAF) + * Feature: Blocca il loop di rendering AR quando disattiviamo gli effetti. + */ if (effectLoopHandle) cancelAnimationFrame(effectLoopHandle); effectLoopHandle = null; effectInferenceInFlight = false; @@ -738,6 +758,11 @@ async function findFace() { } async function startCamera() { + /* [SYSTEM API: navigator.mediaDevices.getUserMedia] + * Funzionamento: Richiede all'utente il permesso di accedere alla webcam/microfono. Restituisce un MediaStream. + * Parametri: object { video: { constraints }, audio: boolean } + * Feature: Accediamo al feed video ad alta risoluzione, specificando il 'facingMode' (user/environment) per il selfie-mode. + */ const stream = await navigator.mediaDevices.getUserMedia({ video: { width: { ideal: 1920 }, @@ -906,9 +931,17 @@ async function init() { ctx.fillText(logText, exportCanvas.width / 2, exportCanvas.height - footerHeight / 2); + /* [SYSTEM API: HTMLCanvasElement.toBlob] + * Funzionamento: Esporta asincronamente il contenuto grafico del canvas in un Blob raw. + */ exportCanvas.toBlob(blob => { const file = new File([blob], "ghostati-makeup.png", { type: "image/png" }); const attemptShare = () => { + /* [SYSTEM API: navigator.share] + * Funzionamento: Invoca il menu di condivisione nativo dell'OS mobile/desktop (Web Share API). + * Parametri: object { title, text, files } + * Feature: Fallback essenziale su mobile dove clipboard.write(Image) è bloccato o difettoso per motivi di sicurezza/supporto. + */ if (navigator.share) { navigator.share({ title: 'Ghostati Makeup', @@ -921,6 +954,11 @@ async function init() { } }; + /* [SYSTEM API: navigator.clipboard.write] + * Funzionamento: API asincrona per scrivere dati arbitrari nella clipboard di sistema. Richiede contesto sicuro (HTTPS/localhost) e interazione utente. + * Parametri: array di ClipboardItem + * Feature: Usata per permettere all'utente di incollare l'immagine PNG esportata direttamente su Telegram/WhatsApp o software di editing. + */ if (navigator.clipboard && navigator.clipboard.write) { try { navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]) diff --git a/scripts/index-effect.js b/scripts/index-effect.js index 990b2c0..d8b1777 100644 --- a/scripts/index-effect.js +++ b/scripts/index-effect.js @@ -199,6 +199,12 @@ function interactionTargetsTopbar(target) { return target instanceof Element && Boolean(target.closest('#ghostTopbar')); } +/* [SYSTEM API: window.addEventListener('pointermove' / 'pointerdown')] + * Funzionamento: API universale che incapsula eventi del mouse, touch e pen/stylus. + * Parametri: evento (es: 'pointermove'), callback(e). + * Feature: Usata per intercettare il movimento dell'utente sull'home page per cancellare l'overlay nero (effetto gratta e vinci), + * ignorando l'interazione se avviene sopra la topbar. + */ window.addEventListener('pointermove', (event) => { if (interactionTargetsTopbar(event.target)) { return; diff --git a/styles/common.css b/styles/common.css new file mode 100644 index 0000000..13c605e --- /dev/null +++ b/styles/common.css @@ -0,0 +1,320 @@ +@import url('https://fonts.googleapis.com/css2?family=League+Script&family=Outfit:wght@400;600;700;800;900&family=JetBrains+Mono:wght@400;700&display=swap'); + +:root { + /* Ghostati specific colors */ + --bg: #0f1115; + --panel: #171a21; + --panel-2: #1d2230; + --text: #eef2ff; + --muted: #aeb6cf; + --line: #2c3346; + --accent: #7aa2ff; + --accent-2: #9f7aea; + --success: #3ddc97; + --warn: #ffcc66; + --danger: #ff7a7a; + --shadow: 0 16px 40px rgba(0, 0, 0, 0.28); + --radius: 18px; + + /* Index specific colors */ + --paper: #ffffff; + --ink: #000000; + --text-on-dark: #ffffff; + --accent-a: #ffffff; + --accent-b: #7ed7ff; + --accent-c: #ffd2ef; +} + +* { + box-sizing: border-box; +} + +html, body { + width: 100%; + height: 100%; + min-height: 100%; + margin: 0; +} + +/* Base typography */ +body { + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + line-height: 1.6; +} + +h1 { + margin: 0 0 10px; + font-family: 'League Script', cursive; + font-size: 60px; + font-weight: 400; + line-height: 1; +} + +h2 { + margin: 0 0 16px; + font-size: 24px; + font-weight: 700; + color: var(--accent); + border-bottom: 1px solid var(--line); + padding-bottom: 8px; +} + +h3 { + font-size: 18px; + color: var(--accent-2); + margin-top: 30px; +} + +p { + color: var(--muted); + margin-bottom: 16px; +} + +pre { + background: #0a0d12; + border: 1px solid var(--line); + border-radius: 12px; + padding: 20px; + overflow-x: auto; + font-size: 14px; + line-height: 1.5; +} + +code { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, fallback; + color: #c6d0f5; +} + +/* --- GHOST TOPBAR --- */ +.ghost-topbar { + position: fixed; + top: 0; + left: 0; + z-index: 20; + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: max(0.72rem, env(safe-area-inset-top)) clamp(0.85rem, 2.5vw, 2rem) 0.72rem; + color: #e8f7ff; + isolation: isolate; + pointer-events: auto; +} + +.ghost-topbar::before { + content: ""; + position: absolute; + inset: 0; + z-index: -2; + background: + linear-gradient(95deg, rgba(9, 14, 22, 0.84), rgba(7, 9, 14, 0.8)), + radial-gradient(circle at 14% 40%, rgba(74, 185, 242, 0.22), transparent 55%), + radial-gradient(circle at 78% 18%, rgba(255, 91, 191, 0.2), transparent 56%); + border-bottom: 1px solid rgba(132, 193, 216, 0.44); + backdrop-filter: blur(8px) saturate(130%); +} + +.ghost-topbar::after { + content: ""; + position: absolute; + inset: 0; + z-index: -1; + background-image: + repeating-linear-gradient( + 0deg, + rgba(200, 240, 255, 0.05) 0, + rgba(200, 240, 255, 0.05) 1px, + transparent 1px, + transparent 3px + ), + repeating-linear-gradient( + 90deg, + rgba(110, 170, 200, 0.06) 0, + rgba(110, 170, 200, 0.06) 1px, + transparent 1px, + transparent 4px + ); + mix-blend-mode: screen; + opacity: 0.9; + animation: topbarNoiseDrift 2.1s steps(4, end) infinite; +} + +.ghost-topbar__progress { + margin: 0; + min-width: 4ch; + font-family: 'JetBrains Mono', monospace; + font-size: clamp(0.86rem, 1.9vw, 1rem); + letter-spacing: 0.14em; + line-height: 1; + color: #8fe8ff; + text-shadow: 0 0 0.65rem rgba(84, 203, 255, 0.52); + font-size: 2.1rem; +} + +.ghost-topbar.is-unlocked .ghost-topbar__progress { + color: transparent; + background-image: linear-gradient( + 120deg, + var(--accent-a) 0%, + var(--accent-b) 38%, + var(--accent-c) 72%, + var(--accent-a) 100% + ); + background-size: 220% 100%; + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + animation: wordmarkGradientShift 2850ms linear infinite; + text-shadow: none; +} + +.ghost-topbar__nav { + margin-left: auto; +} + +.ghost-topbar__links { + list-style: none; + margin: 0; + padding: 0; + display: flex; + align-items: center; + gap: clamp(0.4rem, 1.2vw, 0.75rem); +} + +.ghost-topbar__links a { + display: inline-flex; + align-items: center; + min-height: 2rem; + padding: 0.38rem 0.72rem; + border-radius: 0.36rem; + font-family: 'Outfit', sans-serif; + font-size: clamp(0.72rem, 1.3vw, 0.84rem); + font-weight: 700; + letter-spacing: 0.03em; + text-transform: uppercase; + color: #e9f2f6; + text-decoration: none; + border: 1px solid rgba(182, 221, 240, 0.32); + background: rgba(18, 29, 44, 0.46); + transition: transform 180ms ease, border-color 180ms ease, background 180ms ease; +} + +.ghost-topbar__links a:hover, +.ghost-topbar__links a:focus-visible { + transform: translateY(-1px); + border-color: rgba(173, 230, 255, 0.78); + background: rgba(30, 56, 79, 0.7); +} + +.ghost-topbar__links a:focus-visible { + outline: 2px solid rgba(191, 239, 255, 0.95); + outline-offset: 1px; +} + +.ghost-topbar__toggle { + display: none; + width: 2.45rem; + height: 2.45rem; + border: 1px solid rgba(191, 236, 255, 0.48); + border-radius: 0.4rem; + background: rgba(16, 24, 37, 0.66); + padding: 0.34rem; + align-items: center; + justify-content: center; + cursor: pointer; +} + +.ghost-topbar__toggle span { + display: block; + width: 1.2rem; + height: 0.12rem; + border-radius: 999px; + background: #d9f2ff; + transition: transform 200ms ease, opacity 200ms ease; +} + +.ghost-topbar__toggle span + span { + margin-top: 0.25rem; +} + +.ghost-topbar.is-open .ghost-topbar__toggle span:nth-child(1) { + transform: translateY(0.37rem) rotate(45deg); +} + +.ghost-topbar.is-open .ghost-topbar__toggle span:nth-child(2) { + opacity: 0; +} + +.ghost-topbar.is-open .ghost-topbar__toggle span:nth-child(3) { + transform: translateY(-0.37rem) rotate(-45deg); +} + +@keyframes topbarNoiseDrift { + 0% { + transform: translate(0, 0); + opacity: 0.84; + } + 50% { + transform: translate(-1.2px, 0.8px); + opacity: 0.95; + } + 100% { + transform: translate(0.9px, -0.7px); + opacity: 0.86; + } +} + +@media (max-width: 900px), (pointer: coarse) { + .ghost-topbar { + gap: 0.7rem; + padding-right: max(0.85rem, env(safe-area-inset-right)); + } + + .ghost-topbar__toggle { + display: inline-flex; + } + + .ghost-topbar__nav { + position: fixed; + top: calc(max(0.72rem, env(safe-area-inset-top)) + 2.72rem); + right: max(0.85rem, env(safe-area-inset-right)); + width: min(84vw, 19.5rem); + pointer-events: none; + } + + .ghost-topbar__links { + display: grid; + gap: 0.42rem; + padding: 0.52rem; + border: 1px solid rgba(165, 223, 247, 0.52); + border-radius: 0.56rem; + background: rgba(7, 12, 19, 0.9); + box-shadow: 0 0.7rem 1.5rem rgba(0, 0, 0, 0.35); + transform-origin: top right; + transform: scale(0.96); + opacity: 0; + transition: opacity 170ms ease, transform 170ms ease; + } + + .ghost-topbar__links a { + width: 100%; + justify-content: flex-start; + font-size: 0.74rem; + } + + .ghost-topbar.is-open .ghost-topbar__nav { + pointer-events: auto; + } + + .ghost-topbar.is-open .ghost-topbar__links { + opacity: 1; + transform: scale(1); + } +} + +@media (min-width: 901px) and (pointer: fine) { + .ghost-topbar.is-open .ghost-topbar__nav { + pointer-events: auto; + } +} diff --git a/styles/ghostati-docs.css b/styles/ghostati-docs.css index 64c114e..870049f 100644 --- a/styles/ghostati-docs.css +++ b/styles/ghostati-docs.css @@ -1,30 +1,10 @@ -:root { - --bg: #0f1115; - --panel: #171a21; - --panel-2: #1d2230; - --text: #eef2ff; - --muted: #aeb6cf; - --line: #2c3346; - --accent: #7aa2ff; - --accent-2: #9f7aea; - --success: #3ddc97; - --warn: #ffcc66; - --danger: #ff7a7a; - --shadow: 0 16px 40px rgba(0, 0, 0, 0.28); - --radius: 18px; -} - -* { box-sizing: border-box; } -html, body { height: 100%; } -body { +body.page-docs { margin: 0; background: radial-gradient(circle at top left, rgba(122, 162, 255, 0.14), transparent 28%), radial-gradient(circle at top right, rgba(159, 122, 234, 0.12), transparent 26%), var(--bg); color: var(--text); - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - line-height: 1.6; } .page { @@ -36,88 +16,64 @@ body { gap: 24px; } -header { +.jumbotron { background: linear-gradient(135deg, rgba(122, 162, 255, 0.16), rgba(159, 122, 234, 0.12)); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); - padding: 30px; + padding: 40px; text-align: center; + margin-bottom: 24px; } -h1 { - margin: 0 0 10px; - font-size: 32px; - font-weight: 800; - letter-spacing: 0.02em; +.jumbotron h1 { + color: var(--text); } .subtitle { color: var(--muted); - font-size: 16px; + font-size: 18px; } -.btn-back { - display: inline-block; - margin-top: 20px; - text-decoration: none; - background: linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.03)); - border: 1px solid rgba(255, 255, 255, 0.06); - color: var(--muted); - border-radius: 14px; - font-size: 14px; - font-weight: 600; - padding: 10px 20px; - transition: all 0.2s ease; +.toc { + background: rgba(255, 255, 255, 0.02); + border: 1px solid var(--line); + border-radius: var(--radius); + padding: 24px; + margin-bottom: 24px; } -.btn-back:hover { - background: linear-gradient(180deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.06)); - color: var(--text); - transform: translateY(-1px); +.toc h3 { + margin-top: 0; + margin-bottom: 12px; } -section { - background: linear-gradient(180deg, rgba(255, 255, 255, 0.015), rgba(255, 255, 255, 0)); - border: 1px solid var(--line); - border-radius: var(--radius); - box-shadow: var(--shadow); - padding: 32px; +.toc ul { + list-style: none; + padding: 0; + margin: 0; } -h2 { - margin: 0 0 16px; - font-size: 24px; - font-weight: 700; - color: var(--accent); - border-bottom: 1px solid var(--line); - padding-bottom: 8px; +.toc li { + margin-bottom: 8px; } -h3 { - font-size: 18px; - color: var(--accent-2); - margin-top: 30px; +.toc a { + color: var(--accent); + text-decoration: none; + transition: color 0.2s; } -p { - color: var(--muted); - margin-bottom: 16px; +.toc a:hover { + color: var(--text); } -pre { - background: #0a0d12; +section { + background: linear-gradient(180deg, rgba(255, 255, 255, 0.015), rgba(255, 255, 255, 0)); border: 1px solid var(--line); - border-radius: 12px; - padding: 20px; - overflow-x: auto; - font-size: 14px; - line-height: 1.5; -} - -code { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, fallback; - color: #c6d0f5; + border-radius: var(--radius); + box-shadow: var(--shadow); + padding: 32px; } .block-explain { diff --git a/styles/index.css b/styles/index.css index 96434b1..a2ba946 100644 --- a/styles/index.css +++ b/styles/index.css @@ -1,205 +1,10 @@ -:root { - --paper: #ffffff; - --ink: #000000; - --text-on-dark: #ffffff; - --accent-a: #ffffff; - --accent-b: #7ed7ff; - --accent-c: #ffd2ef; -} - -* { - box-sizing: border-box; -} - -html, -body { - width: 100%; - height: 100%; - min-height: 100%; - margin: 0; -} - -body { +body.page-index { overflow: hidden; min-height: 100svh; background: var(--paper); color: var(--text-on-dark); } -.ghost-topbar { - position: fixed; - top: 0; - left: 0; - z-index: 20; - width: 100%; - display: flex; - align-items: center; - justify-content: space-between; - gap: 1rem; - padding: max(0.72rem, env(safe-area-inset-top)) clamp(0.85rem, 2.5vw, 2rem) 0.72rem; - color: #e8f7ff; - isolation: isolate; - pointer-events: auto; -} - -.ghost-topbar::before { - content: ""; - position: absolute; - inset: 0; - z-index: -2; - background: - linear-gradient(95deg, rgba(9, 14, 22, 0.84), rgba(7, 9, 14, 0.8)), - radial-gradient(circle at 14% 40%, rgba(74, 185, 242, 0.22), transparent 55%), - radial-gradient(circle at 78% 18%, rgba(255, 91, 191, 0.2), transparent 56%); - border-bottom: 1px solid rgba(132, 193, 216, 0.44); - backdrop-filter: blur(8px) saturate(130%); -} - -.ghost-topbar::after { - content: ""; - position: absolute; - inset: 0; - z-index: -1; - background-image: - repeating-linear-gradient( - 0deg, - rgba(200, 240, 255, 0.05) 0, - rgba(200, 240, 255, 0.05) 1px, - transparent 1px, - transparent 3px - ), - repeating-linear-gradient( - 90deg, - rgba(110, 170, 200, 0.06) 0, - rgba(110, 170, 200, 0.06) 1px, - transparent 1px, - transparent 4px - ); - mix-blend-mode: screen; - opacity: 0.9; - animation: topbarNoiseDrift 2.1s steps(4, end) infinite; -} - -.ghost-topbar__progress { - margin: 0; - min-width: 4ch; - font-family: 'JetBrains Mono', monospace; - font-size: clamp(0.86rem, 1.9vw, 1rem); - letter-spacing: 0.14em; - line-height: 1; - color: #8fe8ff; - text-shadow: 0 0 0.65rem rgba(84, 203, 255, 0.52); - font-size: 2.1rem; -} - -.ghost-topbar.is-unlocked .ghost-topbar__progress { - color: transparent; - background-image: linear-gradient( - 120deg, - var(--accent-a) 0%, - var(--accent-b) 38%, - var(--accent-c) 72%, - var(--accent-a) 100% - ); - background-size: 220% 100%; - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - animation: wordmarkGradientShift 2850ms linear infinite; - text-shadow: none; -} - -.ghost-topbar__nav { - margin-left: auto; -} - -.ghost-topbar__links { - list-style: none; - margin: 0; - padding: 0; - display: flex; - align-items: center; - gap: clamp(0.4rem, 1.2vw, 0.75rem); -} - -.ghost-topbar__links a { - display: inline-flex; - align-items: center; - min-height: 2rem; - padding: 0.38rem 0.72rem; - border-radius: 0.36rem; - font-family: 'Outfit', sans-serif; - font-size: clamp(0.72rem, 1.3vw, 0.84rem); - font-weight: 700; - letter-spacing: 0.03em; - text-transform: uppercase; - color: #e9f2f6; - text-decoration: none; - border: 1px solid rgba(182, 221, 240, 0.32); - background: rgba(18, 29, 44, 0.46); - transition: transform 180ms ease, border-color 180ms ease, background 180ms ease; -} - -.ghost-topbar__links a:hover, -.ghost-topbar__links a:focus-visible { - transform: translateY(-1px); - border-color: rgba(173, 230, 255, 0.78); - background: rgba(30, 56, 79, 0.7); -} - -.ghost-topbar__links a:focus-visible { - outline: 2px solid rgba(191, 239, 255, 0.95); - outline-offset: 1px; -} - -.ghost-topbar__toggle { - display: none; - width: 2.45rem; - height: 2.45rem; - border: 1px solid rgba(191, 236, 255, 0.48); - border-radius: 0.4rem; - background: rgba(16, 24, 37, 0.66); - padding: 0.34rem; - align-items: center; - justify-content: center; - cursor: pointer; -} - -.ghost-topbar__toggle span { - display: block; - width: 1.2rem; - height: 0.12rem; - border-radius: 999px; - background: #d9f2ff; - transition: transform 200ms ease, opacity 200ms ease; -} - -.ghost-topbar__toggle span + span { - margin-top: 0.25rem; -} - -.ghost-topbar.is-open .ghost-topbar__toggle span:nth-child(1) { - transform: translateY(0.37rem) rotate(45deg); -} - -.ghost-topbar.is-open .ghost-topbar__toggle span:nth-child(2) { - opacity: 0; -} - -.ghost-topbar.is-open .ghost-topbar__toggle span:nth-child(3) { - transform: translateY(-0.37rem) rotate(-45deg); -} - -@import url('https://fonts.googleapis.com/css2?family=League+Script&family=Outfit:wght@400;600;700;800&family=JetBrains+Mono:wght@400;700&display=swap'); - -h1 { - margin: 0 0 4px; - font-family: 'League Script', cursive; - font-size: 60px; - font-weight: 400; - line-height: 1; -} - .ghost-hero { position: relative; width: 100%; @@ -368,52 +173,6 @@ h1 { } @media (max-width: 900px), (pointer: coarse) { - .ghost-topbar { - gap: 0.7rem; - padding-right: max(0.85rem, env(safe-area-inset-right)); - } - - .ghost-topbar__toggle { - display: inline-flex; - } - - .ghost-topbar__nav { - position: fixed; - top: calc(max(0.72rem, env(safe-area-inset-top)) + 2.72rem); - right: max(0.85rem, env(safe-area-inset-right)); - width: min(84vw, 19.5rem); - pointer-events: none; - } - - .ghost-topbar__links { - display: grid; - gap: 0.42rem; - padding: 0.52rem; - border: 1px solid rgba(165, 223, 247, 0.52); - border-radius: 0.56rem; - background: rgba(7, 12, 19, 0.9); - box-shadow: 0 0.7rem 1.5rem rgba(0, 0, 0, 0.35); - transform-origin: top right; - transform: scale(0.96); - opacity: 0; - transition: opacity 170ms ease, transform 170ms ease; - } - - .ghost-topbar__links a { - width: 100%; - justify-content: flex-start; - font-size: 0.74rem; - } - - .ghost-topbar.is-open .ghost-topbar__nav { - pointer-events: auto; - } - - .ghost-topbar.is-open .ghost-topbar__links { - opacity: 1; - transform: scale(1); - } - .wordmark__layer { width: 90vw; font-size: clamp(3.4rem, 22vw, 8rem); @@ -438,27 +197,6 @@ h1 { } } -@media (min-width: 901px) and (pointer: fine) { - .ghost-topbar.is-open .ghost-topbar__nav { - pointer-events: auto; - } -} - -@keyframes topbarNoiseDrift { - 0% { - transform: translate(0, 0); - opacity: 0.84; - } - 50% { - transform: translate(-1.2px, 0.8px); - opacity: 0.95; - } - 100% { - transform: translate(0.9px, -0.7px); - opacity: 0.86; - } -} - @media (prefers-reduced-motion: reduce) { .wordmark__layer { transition: none;