diff --git a/README.md b/README.md
index 4866cf0..3a508de 100644
--- a/README.md
+++ b/README.md
@@ -1,73 +1,36 @@
-# React + TypeScript + Vite
+## Guardpot Dashboard Tanıtım Turu (React + Vite)
-This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+Step-by-step tıklanabilir bir onboarding bileşeni ile dashboard ekran görüntüsü üzerinde eğitim turu sunar.
-Currently, two official plugins are available:
+### Çalıştırma
-- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
-- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
-
-## React Compiler
-
-The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
-
-## Expanding the ESLint configuration
-
-If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
-
-```js
-export default defineConfig([
- globalIgnores(['dist']),
- {
- files: ['**/*.{ts,tsx}'],
- extends: [
- // Other configs...
-
- // Remove tseslint.configs.recommended and replace with this
- tseslint.configs.recommendedTypeChecked,
- // Alternatively, use this for stricter rules
- tseslint.configs.strictTypeChecked,
- // Optionally, add this for stylistic rules
- tseslint.configs.stylisticTypeChecked,
-
- // Other configs...
- ],
- languageOptions: {
- parserOptions: {
- project: ['./tsconfig.node.json', './tsconfig.app.json'],
- tsconfigRootDir: import.meta.dirname,
- },
- // other options...
- },
- },
-])
+```bash
+cd onboarding-demo
+npm install
+npm run dev
```
-You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
+### Ekran Görüntüsünü Değiştirme
-```js
-// eslint.config.js
-import reactX from 'eslint-plugin-react-x'
-import reactDom from 'eslint-plugin-react-dom'
+- Gerçek görselinizi `src/assets/dashboard.png` dosyasıyla değiştirin. Şu anki dosya bir placeholder metin dosyasıdır; kendi `.png` görselinizi kopyalayın.
-export default defineConfig([
- globalIgnores(['dist']),
- {
- files: ['**/*.{ts,tsx}'],
- extends: [
- // Other configs...
- // Enable lint rules for React
- reactX.configs['recommended-typescript'],
- // Enable lint rules for React DOM
- reactDom.configs.recommended,
- ],
- languageOptions: {
- parserOptions: {
- project: ['./tsconfig.node.json', './tsconfig.app.json'],
- tsconfigRootDir: import.meta.dirname,
- },
- // other options...
- },
- },
-])
+### Dosyalar
+
+- `src/components/OnboardingTour.tsx`: Yüzde bazlı bölgeleri vurgulayan ve popover ile açıklama gösteren tur bileşeni.
+- `src/App.tsx`: Adımların verisini tanımlar ve turu kullanır.
+- `src/App.css`: Stil ve düzen.
+
+### Adım Tanımları
+
+Her adım için:
+
+```ts
+{
+ id: string,
+ title: string,
+ description: string,
+ region: { leftPct: number; topPct: number; widthPct: number; heightPct: number }
+}
```
+
+Değerler görüntünün genişlik/yüksekliğine göre ölçeklenir, böylece responsive çalışır.
diff --git a/src/App.css b/src/App.css
index b9d355d..b4177e4 100644
--- a/src/App.css
+++ b/src/App.css
@@ -1,42 +1,578 @@
+:root {
+ --bg: #0f0f12;
+ --panel: #15161a;
+ --text: #f1f2f5;
+ --muted: #b9bfca;
+ /* Brand: #b02f34 */
+ --primary: #b02f34;
+ --primary-600: #8e262a;
+ --primary-400: #d24449;
+ --ring: rgba(176, 47, 52, 0.45);
+}
+
#root {
- max-width: 1280px;
- margin: 0 auto;
- padding: 2rem;
- text-align: center;
+ width: 100%;
+ margin: 0;
+ padding: 0;
}
-.logo {
- height: 6em;
- padding: 1.5em;
- will-change: filter;
- transition: filter 300ms;
-}
-.logo:hover {
- filter: drop-shadow(0 0 2em #646cffaa);
-}
-.logo.react:hover {
- filter: drop-shadow(0 0 2em #61dafbaa);
+.app-root {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ color: var(--text);
}
-@keyframes logo-spin {
+.app-title {
+ margin: 0;
+ font-size: 1.75rem;
+ letter-spacing: 0.3px;
+ color: var(--primary-400);
+}
+.app-subtitle {
+ margin: 0 0 1rem 0;
+ color: var(--muted);
+}
+
+.tour-root {
+ background: var(--panel);
+ border: 1px solid #262a33;
+ border-radius: 14px;
+ padding: 16px;
+}
+
+.tour-canvas {
+ position: relative;
+ width: 100%;
+ aspect-ratio: 16 / 9;
+ background: #0b0d10;
+ border-radius: 10px;
+ overflow: hidden;
+}
+
+.tour-image {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ display: block;
+}
+
+.tour-overlay {
+ position: absolute;
+ inset: 0;
+ background: radial-gradient(transparent 45%, rgba(0,0,0,0.45));
+ pointer-events: none;
+}
+
+.tour-selective-overlay {
+ position: absolute;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.7);
+ pointer-events: none;
+ transition: background-color 300ms ease;
+}
+
+.tour-highlight-window {
+ position: absolute;
+ background: transparent;
+ pointer-events: none;
+ border-radius: 10px;
+ transition: all 300ms ease;
+ box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.7);
+}
+
+.tour-hotspot {
+ position: absolute;
+ border: 2px dashed rgba(255, 255, 255, 0.25);
+ border-radius: 10px;
+ background: rgba(176, 47, 52, 0.10);
+ transition: all 180ms ease;
+ z-index: 10;
+}
+.tour-hotspot:hover {
+ border-color: var(--primary);
+ box-shadow: 0 0 0 6px rgba(176, 47, 52, 0.18), 0 0 0 1px var(--primary);
+}
+.tour-hotspot.active {
+ border-color: var(--primary);
+ box-shadow: 0 0 0 6px rgba(176, 47, 52, 0.25), 0 0 0 1px var(--primary);
+ background: rgba(176, 47, 52, 0.15);
+}
+
+.tour-popover {
+ position: absolute;
+ transform: translateX(-2%);
+ min-width: 280px;
+ max-width: 420px;
+ background: #111319;
+ border: 1px solid #262a33;
+ border-radius: 12px;
+ padding: 12px 14px;
+ color: var(--text);
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
+}
+.tour-popover.side-left {
+ transform: translateX(-102%);
+}
+.tour-popover.side-right {
+ transform: translateX(2%);
+}
+
+.tour-popover-header {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 6px;
+}
+.tour-step-pill {
+ background: var(--primary);
+ color: white;
+ font-size: 12px;
+ padding: 2px 8px;
+ border-radius: 999px;
+}
+.tour-title {
+ font-weight: 600;
+}
+.tour-desc {
+ color: var(--muted);
+ margin: 6px 0 10px 0;
+}
+.tour-body {
+ display: flex;
+ gap: 10px;
+ align-items: flex-start;
+}
+.tour-body.has-media-left {
+ flex-direction: row;
+}
+.tour-body.has-media-below {
+ flex-direction: column;
+}
+.tour-text { flex: 1; }
+.tour-media {
+ width: 100%;
+ max-height: 220px;
+ object-fit: cover;
+ border-radius: 10px;
+ border: 1px solid #2a2f39;
+ margin: 6px 0 10px 0;
+}
+.media-small-left {
+ max-width: 70%;
+ max-height: 160px;
+ margin: 0 10px 6px 0;
+}
+.media-small-below {
+ max-width: 100%;
+ max-height: 160px;
+}
+.tour-actions {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+.tour-editor { margin: 6px 0 10px 0; }
+.tour-editor .row { display: flex; justify-content: center; gap: 8px; }
+.region-pre {
+ margin-top: 8px;
+ font-size: 12px;
+ background: #0d0f14;
+ padding: 8px;
+ border-radius: 8px;
+ border: 1px solid #222734;
+ color: #cbd5e1;
+}
+.btn {
+ padding: 8px 12px;
+ border-radius: 10px;
+ border: 1px solid #2a2f39;
+ background: #171a21;
+ color: var(--text);
+ cursor: pointer;
+}
+.btn:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+}
+.btn.primary {
+ background: var(--primary);
+ border-color: var(--primary-600);
+}
+.btn.ghost {
+ background: transparent;
+}
+.grow { flex: 1; }
+
+.tour-progress {
+ height: 6px;
+ background: #1b1f27;
+ border-radius: 999px;
+ margin-top: 14px;
+ overflow: hidden;
+}
+.tour-progress-bar {
+ height: 100%;
+ background: linear-gradient(90deg, var(--primary), var(--primary-400));
+}
+
+.tour-step-dots {
+ display: flex;
+ gap: 6px;
+ justify-content: center;
+ margin-top: 10px;
+}
+.dot {
+ width: 10px;
+ height: 10px;
+ border-radius: 999px;
+ background: #2a2f39;
+ border: 1px solid #3a4050;
+}
+.dot.active {
+ background: var(--primary);
+ border-color: var(--ring);
+}
+
+/* Start Screen Styles */
+.start-screen {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ background: var(--panel);
+ border: 1px solid #262a33;
+ border-radius: 14px;
+ padding: 20px;
+}
+
+.dashboard-preview {
+ width: 100%;
+ aspect-ratio: 16 / 9;
+ background: #0b0d10;
+ border-radius: 10px;
+ overflow: hidden;
+ border: 1px solid #262a33;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.dashboard-image {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ display: block;
+}
+
+.start-section {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 20px;
+}
+
+.start-button {
+ padding: 14px 32px;
+ background: var(--primary);
+ color: white;
+ border: none;
+ border-radius: 12px;
+ font-size: 1.1rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 200ms ease;
+ box-shadow: 0 4px 12px rgba(176, 47, 52, 0.3);
+}
+
+.start-button:hover {
+ background: var(--primary-600);
+ transform: translateY(-2px);
+ box-shadow: 0 6px 16px rgba(176, 47, 52, 0.4);
+}
+
+.start-button:active {
+ transform: translateY(0);
+ box-shadow: 0 2px 8px rgba(176, 47, 52, 0.3);
+}
+
+/* New Onboarding Tour Styles */
+.new-tour-container {
+ background: var(--panel);
+ border: 1px solid #262a33;
+ border-radius: 14px;
+ padding: 16px;
+}
+
+.new-tour-image-container {
+ position: relative;
+ width: 100%;
+ aspect-ratio: 16 / 9;
+ background: #0b0d10;
+ border-radius: 10px;
+ overflow: hidden;
+}
+
+.new-tour-image {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ display: block;
+}
+
+.tour-start-overlay {
+ position: absolute;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.7);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.tour-start-button {
+ padding: 16px 40px;
+ background: var(--primary);
+ color: white;
+ border: none;
+ border-radius: 12px;
+ font-size: 1.2rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 200ms ease;
+ box-shadow: 0 4px 12px rgba(176, 47, 52, 0.3);
+}
+
+.tour-start-button:hover {
+ background: var(--primary-600);
+ transform: translateY(-2px);
+ box-shadow: 0 6px 16px rgba(176, 47, 52, 0.4);
+}
+
+.tour-dark-overlay {
+ position: absolute;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.7);
+ pointer-events: none;
+}
+
+.tour-highlight-window {
+ position: absolute;
+ background: transparent;
+ pointer-events: none;
+ border-radius: 10px;
+ transition: all 300ms ease;
+ box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.7);
+}
+
+.tour-step-popup {
+ position: absolute;
+ background: #111319;
+ border: 1px solid #262a33;
+ border-radius: 12px;
+ padding: 20px;
+ color: var(--text);
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
+ min-width: 300px;
+ max-width: 400px;
+ /* Default position - will be overridden by inline styles */
+ top: 20px;
+ right: 20px;
+}
+
+.tour-step-header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 12px;
+}
+
+.tour-step-counter {
+ background: var(--primary);
+ color: white;
+ font-size: 12px;
+ padding: 4px 8px;
+ border-radius: 999px;
+ font-weight: 600;
+}
+
+.tour-step-title {
+ margin: 0;
+ font-weight: 600;
+ font-size: 1.1rem;
+}
+
+.tour-step-description {
+ margin: 0 0 16px 0;
+ color: var(--muted);
+ line-height: 1.5;
+}
+
+.tour-step-actions {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.tour-btn {
+ padding: 8px 16px;
+ border-radius: 8px;
+ border: 1px solid #2a2f39;
+ background: #171a21;
+ color: var(--text);
+ cursor: pointer;
+ font-size: 14px;
+ transition: all 200ms ease;
+}
+
+.tour-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.tour-btn-primary {
+ background: var(--primary);
+ border-color: var(--primary-600);
+ color: white;
+}
+
+.tour-btn-primary:hover:not(:disabled) {
+ background: var(--primary-600);
+}
+
+.tour-btn-secondary:hover:not(:disabled) {
+ background: #1f2329;
+}
+
+.tour-step-dots {
+ display: flex;
+ gap: 6px;
+}
+
+.tour-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: #2a2f39;
+ border: none;
+ cursor: pointer;
+ transition: all 200ms ease;
+}
+
+.tour-dot.active {
+ background: var(--primary);
+}
+
+.tour-dot:hover {
+ background: var(--primary-400);
+}
+
+/* Modal Styles */
+.modal-backdrop {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: var(--panel);
+ display: flex;
+ align-items: stretch;
+ justify-content: stretch;
+ z-index: 1000;
+ animation: fadeIn 0.3s ease-out;
+ overflow: hidden;
+}
+
+.modal-content {
+ background: #0b0d10;
+ border: none;
+ border-radius: 0;
+ width: 100vw;
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ animation: slideIn 0.3s ease-out;
+}
+
+.modal-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 24px;
+ border-bottom: 1px solid #262a33;
+ background: #0f0f12;
+ flex-shrink: 0;
+}
+
+.modal-title {
+ margin: 0;
+ font-size: 1.5rem;
+ font-weight: 600;
+ color: var(--primary-400);
+}
+
+.modal-close-btn {
+ background: none;
+ border: none;
+ color: var(--muted);
+ font-size: 2rem;
+ cursor: pointer;
+ padding: 0;
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 6px;
+ transition: all 200ms ease;
+}
+
+.modal-close-btn:hover {
+ background: #262a33;
+ color: var(--text);
+}
+
+.modal-body {
+ flex: 1;
+ padding: 0;
+ overflow: hidden;
+ background: #0b0d10;
+}
+
+.modal-image-container {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ background: #0b0d10;
+ border-radius: 0;
+ overflow: hidden;
+}
+
+.modal-image {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ display: block;
+}
+
+/* Modal Animations */
+@keyframes fadeIn {
from {
- transform: rotate(0deg);
+ opacity: 0;
}
to {
- transform: rotate(360deg);
+ opacity: 1;
}
}
-@media (prefers-reduced-motion: no-preference) {
- a:nth-of-type(2) .logo {
- animation: logo-spin infinite 20s linear;
+@keyframes slideIn {
+ from {
+ opacity: 0;
+ transform: scale(0.9) translateY(-20px);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1) translateY(0);
}
}
-
-.card {
- padding: 2em;
-}
-
-.read-the-docs {
- color: #888;
-}
diff --git a/src/App.tsx b/src/App.tsx
index 3d7ded3..3f8ba89 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,35 +1,19 @@
-import { useState } from 'react'
-import reactLogo from './assets/react.svg'
-import viteLogo from '/vite.svg'
import './App.css'
+import DashboardTour from './components/DashboardTour'
-function App() {
- const [count, setCount] = useState(0)
-
+export default function App() {
return (
- <>
-
- Vite + React
-
-
-
- Edit src/App.tsx and save to test HMR
-
-
-
- Click on the Vite and React logos to learn more
-
- >
+
+
+
)
}
-
-export default App
diff --git a/src/assets/attackglobe.gif b/src/assets/attackglobe.gif
new file mode 100644
index 0000000..d6b7012
Binary files /dev/null and b/src/assets/attackglobe.gif differ
diff --git a/src/assets/attacksurface1.png b/src/assets/attacksurface1.png
new file mode 100644
index 0000000..300bef5
Binary files /dev/null and b/src/assets/attacksurface1.png differ
diff --git a/src/assets/attacksurface2.png b/src/assets/attacksurface2.png
new file mode 100644
index 0000000..451b778
Binary files /dev/null and b/src/assets/attacksurface2.png differ
diff --git a/src/assets/attacksurface3.png b/src/assets/attacksurface3.png
new file mode 100644
index 0000000..f170fe7
Binary files /dev/null and b/src/assets/attacksurface3.png differ
diff --git a/src/assets/dashboard.jpeg b/src/assets/dashboard.jpeg
new file mode 100644
index 0000000..06731d7
Binary files /dev/null and b/src/assets/dashboard.jpeg differ
diff --git a/src/assets/guardpot.jpeg b/src/assets/guardpot.jpeg
new file mode 100644
index 0000000..ed20deb
Binary files /dev/null and b/src/assets/guardpot.jpeg differ
diff --git a/src/components/AttackSurfaceTour.css b/src/components/AttackSurfaceTour.css
new file mode 100644
index 0000000..efd0c8c
--- /dev/null
+++ b/src/components/AttackSurfaceTour.css
@@ -0,0 +1,465 @@
+/* Attack Surface Tour Styles */
+
+/* Main Preview Container */
+.attack-surface-preview-container {
+ position: relative;
+ aspect-ratio: 16 / 9;
+ background-color: #0b0d10;
+ border-radius: 10px;
+ overflow: hidden;
+}
+
+.attack-surface-preview-image {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ display: block;
+}
+
+.attack-surface-preview-overlay {
+ position: absolute;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.7);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.attack-surface-start-button {
+ padding: 16px 40px;
+ background-color: #b02f34;
+ color: white;
+ border: none;
+ border-radius: 12px;
+ font-size: 1.2rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 200ms ease;
+}
+
+.attack-surface-start-button:hover {
+ background-color: #c0353a;
+ transform: translateY(-2px);
+ box-shadow: 0 4px 20px rgba(176, 47, 52, 0.3);
+}
+
+/* Modal */
+.attack-surface-modal-backdrop {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: #0f0f12;
+ z-index: 1000;
+ animation: attackSurfaceModalBackdropFadeIn 250ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+ overflow: hidden;
+}
+
+.attack-surface-modal-backdrop.closing {
+ animation: attackSurfaceModalBackdropFadeOut 250ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+}
+
+.attack-surface-modal-content {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ background-color: #0b0d10;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+ padding: 20px;
+ animation: attackSurfaceModalContentSlideIn 300ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+}
+
+.attack-surface-modal-content.closing {
+ animation: attackSurfaceModalContentSlideOut 250ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+}
+
+.attack-surface-modal-image-wrapper {
+ position: relative;
+ max-width: 95vw;
+ max-height: 95vh;
+ width: fit-content;
+ height: fit-content;
+ margin: 0 auto;
+}
+
+.attack-surface-modal-image {
+ width: 100%;
+ height: 100%;
+ max-width: 100%;
+ max-height: 100%;
+ object-fit: contain;
+ display: block;
+}
+
+/* Overlay System */
+.attack-surface-overlay-top {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ background: rgba(0, 0, 0, 0.85);
+ pointer-events: none;
+ transition: all 500ms ease-in-out;
+ opacity: 1;
+}
+
+.attack-surface-overlay-left {
+ position: absolute;
+ left: 0;
+ background: rgba(0, 0, 0, 0.85);
+ pointer-events: none;
+ transition: all 500ms ease-in-out;
+ opacity: 1;
+}
+
+.attack-surface-overlay-right {
+ position: absolute;
+ right: 0;
+ background: rgba(0, 0, 0, 0.85);
+ pointer-events: none;
+ transition: all 500ms ease-in-out;
+ opacity: 1;
+}
+
+.attack-surface-overlay-bottom {
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.85);
+ pointer-events: none;
+ transition: all 500ms ease-in-out;
+ opacity: 1;
+}
+
+.attack-surface-overlay-transitioning {
+ opacity: 0.3;
+}
+
+.attack-surface-highlight-border {
+ position: absolute;
+ border: 2px solid #b02f34;
+ border-radius: 10px;
+ pointer-events: none;
+ box-shadow: 0 0 0 2px rgba(176, 47, 52, 0.3);
+ transition: all 500ms ease-in-out;
+ opacity: 1;
+ transform: scale(1);
+}
+
+.attack-surface-highlight-border.transitioning {
+ opacity: 0.5;
+ transform: scale(0.98);
+}
+
+/* Progress Bar */
+.attack-surface-tour-progress-bar {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 8px;
+ background-color: rgba(0, 0, 0, 0.3);
+ display: flex;
+ gap: 2px;
+ z-index: 1001;
+ backdrop-filter: blur(10px);
+ animation: attackSurfaceProgressBarFadeIn 250ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+}
+
+.attack-surface-tour-progress-bar.closing {
+ animation: attackSurfaceProgressBarFadeOut 250ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+}
+
+.attack-surface-progress-segment {
+ flex: 1;
+ position: relative;
+ cursor: pointer;
+ overflow: visible;
+ transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
+ background-color: rgba(255, 255, 255, 0.1);
+}
+
+.attack-surface-progress-segment:hover {
+ background-color: rgba(255, 255, 255, 0.15);
+}
+
+.attack-surface-progress-segment-fill {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(90deg, #b02f34 0%, #d24449 100%);
+ transform: scaleX(0);
+ transform-origin: left;
+ transition: transform 500ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.attack-surface-progress-segment.completed .attack-surface-progress-segment-fill {
+ transform: scaleX(1);
+}
+
+.attack-surface-progress-segment.active {
+ background-color: rgba(176, 47, 52, 0.2);
+}
+
+.attack-surface-progress-segment.active .attack-surface-progress-segment-fill {
+ transform: scaleX(1);
+ animation: attackSurfaceProgressPulse 2s infinite;
+}
+
+.attack-surface-progress-step-number {
+ position: absolute;
+ bottom: 12px;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ background-color: #2a2f39;
+ border: 2px solid #262a33;
+ color: #b9bfca;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 14px;
+ font-weight: 600;
+ transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
+ z-index: 10;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
+}
+
+.attack-surface-progress-segment:hover .attack-surface-progress-step-number {
+ transform: translateX(-50%) scale(1.1);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
+}
+
+.attack-surface-progress-step-number.completed {
+ background-color: #b02f34;
+ border-color: #8e262a;
+ color: white;
+ box-shadow: 0 2px 8px rgba(176, 47, 52, 0.4);
+}
+
+.attack-surface-progress-step-number.active {
+ background: linear-gradient(135deg, #b02f34 0%, #d24449 100%);
+ border-color: #d24449;
+ color: white;
+ box-shadow: 0 4px 16px rgba(176, 47, 52, 0.6);
+ animation: attackSurfaceStepNumberPulse 2s infinite;
+}
+
+@keyframes attackSurfaceProgressPulse {
+ 0%, 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.7;
+ }
+}
+
+@keyframes attackSurfaceStepNumberPulse {
+ 0%, 100% {
+ transform: translateX(-50%) scale(1);
+ box-shadow: 0 4px 16px rgba(176, 47, 52, 0.6);
+ }
+ 50% {
+ transform: translateX(-50%) scale(1.05);
+ box-shadow: 0 6px 20px rgba(176, 47, 52, 0.8);
+ }
+}
+
+@keyframes attackSurfaceProgressBarFadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes attackSurfaceProgressBarFadeOut {
+ from {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ to {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+}
+
+/* Animations */
+@keyframes attackSurfaceModalBackdropFadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes attackSurfaceModalBackdropFadeOut {
+ from {
+ opacity: 1;
+ }
+ to {
+ opacity: 0;
+ }
+}
+
+@keyframes attackSurfaceModalContentSlideIn {
+ from {
+ opacity: 0;
+ transform: scale(0.95);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+@keyframes attackSurfaceModalContentSlideOut {
+ from {
+ opacity: 1;
+ transform: scale(1);
+ }
+ to {
+ opacity: 0;
+ transform: scale(0.95);
+ }
+}
+
+/* Responsive Design */
+@media (max-width: 1024px) {
+ .attack-surface-preview-container {
+ aspect-ratio: 4 / 3;
+ }
+
+ .attack-surface-start-button {
+ padding: 14px 36px;
+ font-size: 1.1rem;
+ }
+}
+
+@media (max-width: 768px) {
+ .attack-surface-preview-container {
+ aspect-ratio: 3 / 2;
+ }
+
+ .attack-surface-start-button {
+ padding: 12px 32px;
+ font-size: 1rem;
+ }
+}
+
+/* Attack Surface Step Popup - Animated */
+.attack-surface-step-popup {
+ position: absolute;
+ background-color: #111319;
+ border: 1px solid #262a33;
+ border-radius: 12px;
+ padding: 20px;
+ color: #f1f2f5;
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(176, 47, 52, 0.3);
+ min-width: 300px;
+ max-width: 400px;
+ animation: attackSurfacePopupSlideIn 600ms ease-out forwards;
+ transition: all 500ms ease-in-out;
+}
+
+.attack-surface-step-popup.transitioning {
+ opacity: 0.3;
+ transition: all 500ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
+}
+
+.attack-surface-step-popup.transitioning.transition-next {
+ transform: translateX(80px) scale(0.95);
+}
+
+.attack-surface-step-popup.transitioning.transition-prev {
+ transform: translateX(-80px) scale(0.95);
+}
+
+.attack-surface-step-popup.closing {
+ opacity: 0;
+ transform: translateY(20px) scale(0.8);
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+/* Popup Animated Border */
+.attack-surface-popup-animated-border {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ border-radius: 12px;
+ border: 2px solid transparent;
+ background: linear-gradient(45deg, rgba(176, 47, 52, 0.3), rgba(176, 47, 52, 0.1));
+ background-clip: border-box;
+ animation: pulseGlow 2s infinite;
+ transition: all 400ms ease-in-out;
+ pointer-events: none;
+}
+
+/* Quit Button */
+.attack-surface-quit-button {
+ position: absolute;
+ top: 12px;
+ right: 12px;
+ width: 28px;
+ height: 28px;
+ background: rgba(0, 0, 0, 0.5);
+ border: 1px solid #262a33;
+ border-radius: 50%;
+ color: #b9bfca;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 16px;
+ font-weight: normal;
+ line-height: 1;
+ z-index: 20;
+ transition: all 200ms ease;
+}
+
+.attack-surface-quit-button:hover {
+ background: rgba(176, 47, 52, 0.8);
+ color: white;
+ transform: scale(1.1);
+}
+
+/* Popup Content */
+.attack-surface-popup-content {
+ position: relative;
+ z-index: 10;
+ transition: all 400ms ease-in-out;
+}
+
+/* Animations */
+@keyframes attackSurfacePopupSlideIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px) scale(0.9);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ }
+}
+
+@keyframes pulseGlow {
+ 0% { box-shadow: 0 0 0 0 rgba(176, 47, 52, 0.4); }
+ 50% { box-shadow: 0 0 8px 4px rgba(176, 47, 52, 0.6); }
+ 100% { box-shadow: 0 0 0 0 rgba(176, 47, 52, 0.4); }
+}
+
diff --git a/src/components/AttackSurfaceTour.tsx b/src/components/AttackSurfaceTour.tsx
new file mode 100644
index 0000000..43412aa
--- /dev/null
+++ b/src/components/AttackSurfaceTour.tsx
@@ -0,0 +1,380 @@
+import { useState } from 'react'
+import attackSurface1 from '../assets/attacksurface1.png'
+import attackSurface2 from '../assets/attacksurface2.png'
+import attackSurface3 from '../assets/attacksurface3.png'
+import './AttackSurfaceTour.css'
+
+type TourStep = {
+ id: string
+ title: string
+ description: string
+ image: string
+ region: { leftPct: number; topPct: number; widthPct: number; heightPct: number }
+ popupPosition?: { leftPct?: number; topPct?: number; rightPct?: number; bottomPct?: number }
+}
+
+const attackSurfaceSteps: TourStep[] = [
+ {
+ id: 'overview',
+ title: 'Attack Surface Overview',
+ description: 'Attack Surface sayfasına hoş geldiniz! Bu sayfada sisteminizdeki potansiyel saldırı yüzeylerini analiz edebilir ve güvenlik açıklarını tespit edebilirsiniz.',
+ image: attackSurface1,
+ region: { leftPct: 0, topPct: 0, widthPct: 100, heightPct: 100 },
+ popupPosition: { rightPct: 31, topPct: 25 },
+ },
+ {
+ id: 'network-visualization',
+ title: 'Network Visualization',
+ description: 'Bu bölümde ağınızın görsel haritasını görebilirsiniz. Bağlantılar ve nodlar arasındaki ilişkileri kolayca takip edebilirsiniz.',
+ image: attackSurface1,
+ region: { leftPct: 15, topPct: 2, widthPct: 82, heightPct: 12 },
+ popupPosition: { rightPct: 3, topPct: 16 },
+ },
+ {
+ id: 'asset-list',
+ title: 'Asset List',
+ description: 'Sisteminizdeki tüm varlıkların listesi burada görüntülenir. Her bir varlığın detaylarına bu bölümden erişebilirsiniz.',
+ image: attackSurface1,
+ region: { leftPct: 15, topPct: 16, widthPct: 83, heightPct: 80 },
+ popupPosition: { leftPct: 17, bottomPct: 59 },
+ },
+ {
+ id: 'detailed-analysis',
+ title: 'Detailed Analysis View',
+ description: 'Şimdi detaylı analiz ekranına geçiyoruz. Bu ekranda seçili varlığınız hakkında daha kapsamlı bilgilere ulaşabilirsiniz.',
+ image: attackSurface2,
+ region: { leftPct: 14, topPct: 0, widthPct: 85, heightPct: 100 },
+ popupPosition: { rightPct: 3, topPct:4 },
+ },
+ {
+ id: 'vulnerability-score',
+ title: 'Vulnerability Score',
+ description: 'Üst bölümde varlığınızın risk skorunu ve güvenlik durumunu görebilirsiniz. Kritik bulgular burada vurgulanır.',
+ image: attackSurface2,
+ region: { leftPct: 15, topPct: 16, widthPct: 83, heightPct: 10 },
+ popupPosition: { rightPct: 60, topPct: 28 },
+ },
+ {
+ id: 'threat-details',
+ title: 'Threat Details',
+ description: 'Bu bölümde tespit edilen tehditlerin detayları yer alır. Her bir tehdidin öncelik seviyesi ve açıklamasını inceleyebilirsiniz.',
+ image: attackSurface2,
+ region: { leftPct: 15, topPct: 30, widthPct: 84, heightPct: 70 },
+ popupPosition: { leftPct: 17, topPct: 75 },
+ },
+ {
+ id: 'mitigation-actions',
+ title: 'Mitigation Actions',
+ description: 'Sağ tarafta tehditleri azaltmak için önerilen aksiyonlar listelenir. Bu önerileri uygulayarak güvenliğinizi artırabilirsiniz.',
+ image: attackSurface2,
+ region: { leftPct: 92, topPct: 39, widthPct: 8, heightPct: 20 },
+ popupPosition: { rightPct: 10, topPct: 39 },
+ },
+
+ {
+ id: 'global-threat-map',
+ title: 'Global Threat Map',
+ description: 'Son olarak, global tehdit haritasını görüntülüyoruz. Bu haritada dünyanın farklı bölgelerinden gelen tehditleri ve saldırı paternlerini takip edebilirsiniz.',
+ image: attackSurface3,
+ region: { leftPct: 28, topPct: 6, widthPct: 44, heightPct: 88 },
+ popupPosition: { rightPct: 2, topPct: 10 },
+ },
+]
+
+type AttackSurfaceTourProps = {
+ autoStart?: boolean
+ onClose?: () => void
+}
+
+export default function AttackSurfaceTour({ autoStart = false, onClose }: AttackSurfaceTourProps) {
+ const [isModalOpen, setIsModalOpen] = useState(autoStart)
+ const [currentStep, setCurrentStep] = useState(0)
+ const [isTransitioning, setIsTransitioning] = useState(false)
+ const [isModalAnimating, setIsModalAnimating] = useState(false)
+ const [transitionDirection, setTransitionDirection] = useState<'next' | 'prev'>('next')
+
+ const handleStartTour = () => {
+ setIsModalOpen(true)
+ setCurrentStep(0)
+ }
+
+ const handleClose = () => {
+ setIsModalAnimating(true)
+ // Parent'ı hemen bilgilendir
+ if (onClose) {
+ onClose()
+ }
+ setTimeout(() => {
+ setIsModalOpen(false)
+ setCurrentStep(0)
+ setIsTransitioning(false)
+ setTimeout(() => {
+ setIsModalAnimating(false)
+ }, 50)
+ }, 250)
+ }
+
+ const handleNext = () => {
+ if (currentStep < attackSurfaceSteps.length - 1) {
+ setTransitionDirection('next')
+ setIsTransitioning(true)
+ setTimeout(() => {
+ setCurrentStep(currentStep + 1)
+ setIsTransitioning(false)
+ }, 500)
+ }
+ }
+
+ const handlePrevious = () => {
+ if (currentStep > 0) {
+ setTransitionDirection('prev')
+ setIsTransitioning(true)
+ setTimeout(() => {
+ setCurrentStep(currentStep - 1)
+ setIsTransitioning(false)
+ }, 500)
+ }
+ }
+
+ const handleStepClick = (stepIndex: number) => {
+ if (stepIndex !== currentStep) {
+ setTransitionDirection(stepIndex > currentStep ? 'next' : 'prev')
+ setIsTransitioning(true)
+ setTimeout(() => {
+ setCurrentStep(stepIndex)
+ setIsTransitioning(false)
+ }, 500)
+ }
+ }
+
+ const activeStep = attackSurfaceSteps[currentStep]
+
+ return (
+ <>
+ {/* Main Preview - only show if not autoStart */}
+ {!autoStart && (
+
+

+
+
+
+
+
+ )}
+
+ {/* Modal */}
+ {isModalOpen && (
+ <>
+
+
e.stopPropagation()}
+ >
+
+

+
+ {/* Dark overlay with highlight window - only show if not first step */}
+ {currentStep > 0 && (
+ <>
+ {/* Top overlay */}
+ {activeStep.region.topPct > 0 && (
+
+ )}
+
+ {/* Left overlay */}
+ {activeStep.region.leftPct > 0 && (
+
+ )}
+
+ {/* Right overlay */}
+ {activeStep.region.leftPct + activeStep.region.widthPct < 100 && (
+
+ )}
+
+ {/* Bottom overlay */}
+ {activeStep.region.topPct + activeStep.region.heightPct < 100 && (
+
+ )}
+
+ {/* Highlight border */}
+
+ >
+ )}
+
+ {/* Step info popup */}
+
+ {/* Subtle animated border */}
+
+
+ {/* Quit button */}
+
+
+ {/* Content with higher z-index */}
+
+
+
+ {currentStep + 1} / {attackSurfaceSteps.length}
+
+
+ {activeStep.title}
+
+
+
+ {activeStep.description}
+
+
+
+
+
+ {currentStep === attackSurfaceSteps.length - 1 ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+ {/* Progress Bar - only show when modal is open */}
+ {!isModalAnimating && (
+
+ {attackSurfaceSteps.map((step, index) => (
+
handleStepClick(index)}
+ title={step.title}
+ >
+
+
+ {index + 1}
+
+
+ ))}
+
+ )}
+ >
+ )}
+
+ {/* CSS Animations */}
+
+ >
+ )
+}
+
diff --git a/src/components/DashboardTour.css b/src/components/DashboardTour.css
new file mode 100644
index 0000000..c23fd3b
--- /dev/null
+++ b/src/components/DashboardTour.css
@@ -0,0 +1,850 @@
+/* DashboardTour Component Styles - Enhanced Classic Layout */
+
+/* Main Container */
+.dashboard-tour-container {
+ display: flex;
+ gap: 32px;
+ background: linear-gradient(135deg, rgba(21, 22, 26, 0.95) 0%, rgba(17, 19, 25, 0.95) 100%);
+ border: 1px solid rgba(176, 47, 52, 0.2);
+ border-radius: 24px;
+ padding: 32px;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(176, 47, 52, 0.1);
+ backdrop-filter: blur(10px);
+ animation: containerFadeIn 600ms cubic-bezier(0.4, 0, 0.2, 1);
+ overflow: hidden;
+}
+
+/* Tour Selection Menu */
+.tour-selection-menu {
+ width: 340px;
+ background: linear-gradient(135deg, rgba(17, 19, 25, 0.8) 0%, rgba(15, 15, 18, 0.8) 100%);
+ border: 1px solid rgba(38, 42, 51, 0.8);
+ border-radius: 20px;
+ padding: 24px;
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
+ animation: slideInLeft 600ms cubic-bezier(0.4, 0, 0.2, 1) 200ms backwards;
+ overflow: hidden;
+}
+
+.tour-selection-title {
+ margin: 0 0 24px 0;
+ color: #d24449;
+ font-size: 1.5rem;
+ font-weight: 700;
+ letter-spacing: -0.02em;
+ text-shadow: 0 2px 10px rgba(210, 68, 73, 0.3);
+}
+
+.tour-options-container {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.tour-option-button {
+ position: relative;
+ padding: 20px 24px;
+ border-radius: 16px;
+ cursor: pointer;
+ font-size: 15px;
+ font-weight: 400;
+ text-align: left;
+ transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
+ border: 1px solid rgba(42, 47, 57, 0.5);
+ background: linear-gradient(135deg, rgba(23, 26, 33, 0.6) 0%, rgba(17, 19, 25, 0.6) 100%);
+ color: #f1f2f5;
+ overflow: hidden;
+}
+
+.tour-option-button::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(135deg, rgba(176, 47, 52, 0.1) 0%, rgba(210, 68, 73, 0.05) 100%);
+ opacity: 0;
+ transition: opacity 300ms;
+ pointer-events: none;
+}
+
+.tour-option-button:hover::before {
+ opacity: 1;
+}
+
+.tour-option-button.selected {
+ background: linear-gradient(135deg, rgba(176, 47, 52, 0.2) 0%, rgba(142, 38, 42, 0.15) 100%);
+ border-color: rgba(176, 47, 52, 0.6);
+ transform: translateX(4px);
+ box-shadow: 0 8px 24px rgba(176, 47, 52, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.1);
+}
+
+.tour-option-button.selected::before {
+ opacity: 1;
+}
+
+.tour-option-button:hover:not(.selected):not(.disabled) {
+ background: linear-gradient(135deg, rgba(26, 29, 37, 0.8) 0%, rgba(23, 26, 33, 0.8) 100%);
+ border-color: rgba(58, 63, 73, 0.8);
+ transform: translateX(2px);
+}
+
+.tour-option-button.disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+}
+
+.tour-option-button.disabled:hover {
+ transform: none;
+ background: linear-gradient(135deg, rgba(23, 26, 33, 0.6) 0%, rgba(17, 19, 25, 0.6) 100%);
+ border-color: rgba(42, 47, 57, 0.5);
+}
+
+.tour-option-content {
+ position: relative;
+ z-index: 1;
+}
+
+.tour-option-title {
+ font-weight: 600;
+ margin-bottom: 6px;
+ font-size: 17px;
+ color: #f1f2f5;
+ transition: color 300ms;
+}
+
+.tour-option-button.selected .tour-option-title {
+ color: #ff6b6b;
+ text-shadow: 0 0 10px rgba(255, 107, 107, 0.3);
+}
+
+.tour-option-description {
+ font-size: 13px;
+ opacity: 0.75;
+ color: #b9bfca;
+ line-height: 1.4;
+}
+
+.tour-option-button.selected .tour-option-description {
+ color: rgba(255, 255, 255, 0.85);
+ opacity: 1;
+}
+
+.disabled-badge {
+ position: absolute;
+ top: 12px;
+ right: 12px;
+ padding: 3px 10px;
+ background: rgba(156, 163, 175, 0.15);
+ border: 1px solid rgba(156, 163, 175, 0.3);
+ border-radius: 999px;
+ font-size: 10px;
+ color: #9ca3af;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+/* Main Preview */
+.main-preview-container {
+ flex: 1;
+ background: linear-gradient(135deg, rgba(11, 13, 16, 0.6) 0%, rgba(15, 15, 18, 0.6) 100%);
+ border: 1px solid rgba(38, 42, 51, 0.5);
+ border-radius: 20px;
+ overflow: hidden;
+ min-height: 600px;
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
+ animation: slideInRight 600ms cubic-bezier(0.4, 0, 0.2, 1) 200ms backwards;
+}
+
+.preview-image-container {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.preview-image {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ display: block;
+ transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.preview-image-container:hover .preview-image {
+ transform: scale(1.02);
+}
+
+.preview-overlay {
+ position: absolute;
+ inset: 0;
+ background: radial-gradient(circle at center, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.8) 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: background 250ms ease;
+}
+
+.preview-image-container:hover .preview-overlay {
+ background: radial-gradient(circle at center, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.75) 100%);
+}
+
+.start-tour-button {
+ display: inline-flex;
+ align-items: center;
+ gap: 12px;
+ padding: 16px 32px;
+ background: linear-gradient(135deg, #b02f34 0%, #d24449 100%);
+ color: white;
+ border: none;
+ border-radius: 14px;
+ font-size: 1.1rem;
+ font-weight: 600;
+ cursor: pointer;
+ box-shadow: 0 8px 24px rgba(176, 47, 52, 0.35), 0 0 0 1px rgba(255, 255, 255, 0.1) inset;
+ transition: all 350ms cubic-bezier(0.4, 0, 0.2, 1);
+ transform: scale(1);
+ letter-spacing: -0.01em;
+}
+
+.start-tour-button:hover {
+ background: linear-gradient(135deg, #c0353a 0%, #e04e53 100%);
+ transform: translateY(-4px) scale(1.03);
+ box-shadow: 0 12px 36px rgba(176, 47, 52, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.15) inset;
+}
+
+.start-tour-button:active {
+ transform: translateY(-1px) scale(1.01);
+ box-shadow: 0 6px 20px rgba(176, 47, 52, 0.4);
+}
+
+.start-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 50%;
+ font-size: 0.95rem;
+ transition: all 350ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.start-tour-button:hover .start-icon {
+ background: rgba(255, 255, 255, 0.3);
+ transform: scale(1.1) translateX(2px);
+}
+
+/* Tour Modal Container - for GuardpotTour and AttackSurfaceTour */
+.tour-modal-container {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: #0f0f12;
+ z-index: 1000;
+ animation: modalBackdropFadeIn 250ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+ overflow: hidden;
+}
+
+/* Modal */
+.modal-backdrop {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: #0f0f12;
+ z-index: 1000;
+ animation: modalBackdropFadeIn 250ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+ overflow: hidden;
+}
+
+.modal-backdrop.closing {
+ animation: modalBackdropFadeOut 250ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+}
+
+.modal-content {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ background-color: #0b0d10;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+ animation: modalContentSlideIn 300ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+}
+
+.modal-content.closing {
+ animation: modalContentSlideOut 250ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+}
+
+.dashboard-modal {
+ padding: 20px;
+}
+
+.modal-image-wrapper {
+ position: relative;
+ max-width: 95vw;
+ max-height: 95vh;
+ width: fit-content;
+ height: fit-content;
+ margin: 0 auto;
+}
+
+.modal-image {
+ width: 100%;
+ height: 100%;
+ max-width: 100%;
+ max-height: 100%;
+ object-fit: contain;
+ display: block;
+}
+
+/* Overlay System */
+.overlay-top {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ background: rgba(0, 0, 0, 0.85);
+ pointer-events: none;
+ transition: all 500ms ease-in-out;
+ opacity: 1;
+}
+
+.overlay-left {
+ position: absolute;
+ left: 0;
+ background: rgba(0, 0, 0, 0.85);
+ pointer-events: none;
+ transition: all 500ms ease-in-out;
+ opacity: 1;
+}
+
+.overlay-right {
+ position: absolute;
+ right: 0;
+ background: rgba(0, 0, 0, 0.85);
+ pointer-events: none;
+ transition: all 500ms ease-in-out;
+ opacity: 1;
+}
+
+.overlay-bottom {
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.85);
+ pointer-events: none;
+ transition: all 500ms ease-in-out;
+ opacity: 1;
+}
+
+.overlay-transitioning {
+ opacity: 0.5;
+}
+
+/* Highlight Border */
+.highlight-border {
+ position: absolute;
+ border: 2px solid #b02f34;
+ border-radius: 10px;
+ pointer-events: none;
+ transition: all 500ms ease-in-out;
+ box-shadow: 0 0 0 2px rgba(176, 47, 52, 0.3), 0 0 15px rgba(176, 47, 52, 0.2);
+ opacity: 1;
+ transform: scale(1);
+}
+
+.highlight-border.transitioning {
+ opacity: 0.5;
+ transform: scale(0.98);
+}
+
+/* Step Info Popup */
+.step-info-popup {
+ position: absolute;
+ background-color: #111319;
+ border: 1px solid #262a33;
+ border-radius: 12px;
+ padding: 20px;
+ color: #f1f2f5;
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(176, 47, 52, 0.3);
+ min-width: 300px;
+ max-width: 400px;
+ animation: popupSlideIn 600ms ease-out forwards;
+ transition: all 500ms ease-in-out;
+}
+
+.step-info-popup.transitioning {
+ opacity: 0.3;
+ transition: all 500ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
+}
+
+.step-info-popup.transitioning.transition-next {
+ transform: translateX(80px) scale(0.95);
+}
+
+.step-info-popup.transitioning.transition-prev {
+ transform: translateX(-80px) scale(0.95);
+}
+
+.step-info-popup.closing {
+ animation: popupSlideOut 250ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+}
+
+/* Popup Animated Border */
+.popup-animated-border {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ border-radius: 12px;
+ border: 2px solid transparent;
+ background: linear-gradient(45deg, rgba(176, 47, 52, 0.3), rgba(176, 47, 52, 0.1));
+ background-clip: border-box;
+ animation: pulseGlow 2s infinite;
+ transition: all 400ms ease-in-out;
+}
+
+.step-info-popup.transitioning .popup-animated-border {
+ pointer-events: none;
+}
+
+/* Quit Button */
+.quit-button {
+ position: absolute;
+ top: 12px;
+ right: 12px;
+ width: 28px;
+ height: 28px;
+ background: rgba(0, 0, 0, 0.5);
+ border: 1px solid #262a33;
+ border-radius: 50%;
+ color: #b9bfca;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 16px;
+ font-weight: normal;
+ line-height: 1;
+ z-index: 20;
+ transition: all 200ms ease;
+}
+
+.quit-button:hover {
+ background: rgba(176, 47, 52, 0.8);
+ color: white;
+ transform: scale(1.1);
+}
+
+/* Popup Content */
+.popup-content {
+ position: relative;
+ z-index: 10;
+ transition: all 400ms ease-in-out;
+}
+
+.popup-header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 12px;
+}
+
+.step-counter {
+ background-color: #b02f34;
+ color: white;
+ font-size: 12px;
+ padding: 4px 8px;
+ border-radius: 999px;
+ font-weight: 600;
+}
+
+.popup-title {
+ margin: 0;
+ font-weight: 600;
+ font-size: 1.1rem;
+}
+
+.popup-description {
+ margin: 0 0 16px 0;
+ color: #b9bfca;
+ line-height: 1.5;
+}
+
+.popup-navigation {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.nav-button {
+ padding: 8px 16px;
+ border-radius: 8px;
+ border: 1px solid #2a2f39;
+ background-color: #171a21;
+ color: #f1f2f5;
+ cursor: pointer;
+ font-size: 14px;
+ transition: all 200ms ease;
+}
+
+.nav-button:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.nav-button:hover:not(:disabled) {
+ background-color: #1a1d25;
+ border-color: #3a3f49;
+}
+
+.nav-button.primary {
+ border-color: #8e262a;
+ background-color: #b02f34;
+ color: white;
+}
+
+.nav-button.primary:hover {
+ background-color: #c0353a;
+}
+
+/* Progress Bar - Dashboard specific styles */
+.dashboard-progress-bar {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 8px;
+ background-color: rgba(0, 0, 0, 0.3);
+ display: flex;
+ gap: 2px;
+ z-index: 1001;
+ backdrop-filter: blur(10px);
+ animation: progressBarFadeIn 250ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+}
+
+.dashboard-progress-bar.closing {
+ animation: progressBarFadeOut 250ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+}
+
+.dashboard-progress-segment {
+ flex: 1;
+ position: relative;
+ cursor: pointer;
+ overflow: visible;
+ transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
+ background-color: rgba(255, 255, 255, 0.05);
+}
+
+.dashboard-progress-segment:hover {
+ background-color: rgba(255, 255, 255, 0.1);
+}
+
+.dashboard-progress-segment-fill {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: transparent;
+ transform: scaleX(0);
+ transform-origin: left;
+ opacity: 0;
+ transition: transform 400ms cubic-bezier(0.4, 0, 0.2, 1), opacity 250ms ease;
+}
+
+.dashboard-progress-segment.completed .dashboard-progress-segment-fill {
+ background: linear-gradient(90deg, #8e262a 0%, #b02f34 100%);
+ transform: scaleX(1);
+ opacity: 1;
+}
+
+.dashboard-progress-segment.active .dashboard-progress-segment-fill {
+ background: linear-gradient(90deg, #b02f34 0%, #d24449 100%);
+ transform: scaleX(1);
+ opacity: 1;
+ animation: progressFillPulse 2s infinite;
+}
+
+.dashboard-progress-step-number {
+ position: absolute;
+ bottom: 12px;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ background-color: #1a1d25;
+ border: 2px solid #262a33;
+ color: #6b7280;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 14px;
+ font-weight: 600;
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
+ z-index: 10;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
+}
+
+.dashboard-progress-segment:hover .dashboard-progress-step-number {
+ transform: translateX(-50%) scale(1.1);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
+}
+
+.dashboard-progress-step-number.completed {
+ background-color: #8e262a;
+ border-color: #b02f34;
+ color: #f1f2f5;
+ box-shadow: 0 2px 8px rgba(176, 47, 52, 0.3);
+}
+
+.dashboard-progress-step-number.active {
+ background: linear-gradient(135deg, #b02f34 0%, #d24449 100%);
+ border-color: #d24449;
+ color: white;
+ box-shadow: 0 4px 16px rgba(176, 47, 52, 0.6);
+ animation: stepNumberPulse 2s infinite;
+}
+
+/* Animations */
+@keyframes containerFadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes slideInLeft {
+ from {
+ opacity: 0;
+ transform: translateX(-30px);
+ }
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+@keyframes slideInRight {
+ from {
+ opacity: 0;
+ transform: translateX(30px);
+ }
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+@keyframes progressFillPulse {
+ 0%, 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.85;
+ }
+}
+
+@keyframes progressBarFadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes progressBarFadeOut {
+ from {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ to {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+}
+
+@keyframes stepNumberPulse {
+ 0%, 100% {
+ transform: translateX(-50%) scale(1);
+ box-shadow: 0 4px 16px rgba(176, 47, 52, 0.6);
+ }
+ 50% {
+ transform: translateX(-50%) scale(1.05);
+ box-shadow: 0 6px 20px rgba(176, 47, 52, 0.8);
+ }
+}
+
+@keyframes pulseGlow {
+ 0% { box-shadow: 0 0 0 0 rgba(176, 47, 52, 0.4); }
+ 50% { box-shadow: 0 0 8px 4px rgba(176, 47, 52, 0.6); }
+ 100% { box-shadow: 0 0 0 0 rgba(176, 47, 52, 0.4); }
+}
+
+@keyframes modalBackdropFadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+@keyframes modalBackdropFadeOut {
+ from { opacity: 1; }
+ to { opacity: 0; }
+}
+
+@keyframes modalContentSlideIn {
+ from {
+ opacity: 0;
+ transform: scale(0.95);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+@keyframes modalContentSlideOut {
+ from {
+ opacity: 1;
+ transform: scale(1);
+ }
+ to {
+ opacity: 0;
+ transform: scale(0.95);
+ }
+}
+
+@keyframes popupSlideIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px) scale(0.9);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ }
+}
+
+@keyframes popupSlideOut {
+ from {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ }
+ to {
+ opacity: 0;
+ transform: translateY(20px) scale(0.9);
+ }
+}
+
+/* Responsive Design */
+@media (max-width: 1200px) {
+ .dashboard-tour-container {
+ gap: 24px;
+ padding: 24px;
+ }
+
+ .tour-selection-menu {
+ width: 300px;
+ }
+}
+
+@media (max-width: 1024px) {
+ .dashboard-tour-container {
+ flex-direction: column;
+ gap: 20px;
+ }
+
+ .tour-selection-menu {
+ width: 100%;
+ }
+
+ .main-preview-container {
+ min-height: 500px;
+ }
+}
+
+@media (max-width: 768px) {
+ .dashboard-tour-container {
+ padding: 20px;
+ gap: 16px;
+ border-radius: 16px;
+ }
+
+ .tour-selection-menu {
+ padding: 20px;
+ border-radius: 16px;
+ }
+
+ .tour-selection-title {
+ font-size: 1.3rem;
+ }
+
+ .tour-option-button {
+ padding: 16px 20px;
+ }
+
+ .tour-option-title {
+ font-size: 16px;
+ }
+
+ .tour-option-description {
+ font-size: 12px;
+ }
+
+ .start-tour-button {
+ padding: 14px 28px;
+ font-size: 1rem;
+ }
+
+ .start-icon {
+ width: 26px;
+ height: 26px;
+ font-size: 0.9rem;
+ }
+
+ .step-info-popup {
+ min-width: 280px;
+ max-width: 320px;
+ padding: 16px;
+ }
+}
+
+@media (max-width: 480px) {
+ .dashboard-tour-container {
+ padding: 16px;
+ }
+
+ .tour-selection-title {
+ font-size: 1.2rem;
+ }
+
+ .start-tour-button {
+ padding: 13px 26px;
+ font-size: 0.95rem;
+ gap: 10px;
+ }
+
+ .start-icon {
+ width: 24px;
+ height: 24px;
+ font-size: 0.85rem;
+ }
+}
diff --git a/src/components/DashboardTour.tsx b/src/components/DashboardTour.tsx
new file mode 100644
index 0000000..596b068
--- /dev/null
+++ b/src/components/DashboardTour.tsx
@@ -0,0 +1,297 @@
+import { useState } from 'react'
+import dashboardImg from '../assets/dashboard.jpeg'
+import guardpotImg from '../assets/guardpot.jpeg'
+import attackSurfaceImg from '../assets/attacksurface1.png'
+import GuardpotTour from './GuardpotTour'
+import AttackSurfaceTour from './AttackSurfaceTour'
+import './DashboardTour.css'
+
+type TourStep = {
+ id: string
+ title: string
+ description: string
+ region: { leftPct: number; topPct: number; widthPct: number; heightPct: number }
+ popupPosition?: { leftPct?: number; topPct?: number; rightPct?: number; bottomPct?: number }
+}
+
+const dashboardSteps: TourStep[] = [
+ {
+ id: 'overview',
+ title: 'Dashboard Overview',
+ description: "Guardpot Dashboard'a hoş geldiniz! Bu genel bakış ekranında sistem durumunuzu, aktif bağlantıları ve güvenlik metriklerinizi görebilirsiniz.",
+ region: { leftPct: 0, topPct: 0, widthPct: 100, heightPct: 100 },
+ popupPosition: { rightPct: 1, topPct: 25 },
+ },
+ {
+ id: 'navigation-menu',
+ title: 'Navigation Menu',
+ description: 'Sol taraftaki menü ile Dashboard, Guardpots, Attack Surface gibi farklı bölümlere erişebilirsiniz. Şu anda Dashboard bölümündesiniz.',
+ region: { leftPct: 32, topPct: 1, widthPct: 67, heightPct: 76 },
+ popupPosition: { rightPct: 70, topPct: 20 },
+ },
+ {
+ id: 'network-graph',
+ title: 'Network Graph',
+ description: 'Ana network grafiği ile sistem durumunuzu görsel olarak takip edebilirsiniz. Merkezi "G" noktasından dünya çapındaki bağlantılarınızı görebilirsiniz.',
+ region: { leftPct: 15, topPct: 77, widthPct: 83, heightPct: 20 },
+ popupPosition: { rightPct: 5, bottomPct: 25 },
+ },
+]
+
+const tourOptions = [
+ { id: 'dashboard', name: 'Dashboard', description: 'Dashboard overview and features' },
+ { id: 'guardpot', name: 'Guardpot', description: 'Guardpot management system' },
+ { id: 'attack-surface', name: 'Attack Surface', description: 'Attack surface analysis' },
+ { id: 'secure-link', name: 'Secure Link', description: 'Secure link management', disabled: true },
+ { id: 'virtual-guarded-network', name: 'Virtual Guarded Network', description: 'Virtual network configuration', disabled: true }
+]
+
+export default function DashboardTour() {
+ const [isModalOpen, setIsModalOpen] = useState(false)
+ const [currentStep, setCurrentStep] = useState(0)
+ const [isTransitioning, setIsTransitioning] = useState(false)
+ const [isModalAnimating, setIsModalAnimating] = useState(false)
+ const [selectedTour, setSelectedTour] = useState('dashboard')
+ const [isClosing, setIsClosing] = useState(false)
+ const [transitionDirection, setTransitionDirection] = useState<'next' | 'prev'>('next')
+
+ const handleStartTour = () => {
+ setIsModalOpen(true)
+ setCurrentStep(0)
+ setIsClosing(false)
+ }
+
+ const handleClose = () => {
+ setIsModalAnimating(true)
+ setIsClosing(true)
+ setTimeout(() => {
+ setIsModalOpen(false)
+ setCurrentStep(0)
+ setIsTransitioning(false)
+ setTimeout(() => {
+ setIsModalAnimating(false)
+ setIsClosing(false)
+ }, 50)
+ }, 250)
+ }
+
+ const handleNext = () => {
+ if (currentStep < dashboardSteps.length - 1) {
+ setTransitionDirection('next')
+ setIsTransitioning(true)
+ setTimeout(() => {
+ setCurrentStep(currentStep + 1)
+ setIsTransitioning(false)
+ }, 500)
+ }
+ }
+
+ const handlePrevious = () => {
+ if (currentStep > 0) {
+ setTransitionDirection('prev')
+ setIsTransitioning(true)
+ setTimeout(() => {
+ setCurrentStep(currentStep - 1)
+ setIsTransitioning(false)
+ }, 500)
+ }
+ }
+
+ const handleStepClick = (stepIndex: number) => {
+ if (stepIndex !== currentStep) {
+ setTransitionDirection(stepIndex > currentStep ? 'next' : 'prev')
+ setIsTransitioning(true)
+ setTimeout(() => {
+ setCurrentStep(stepIndex)
+ setIsTransitioning(false)
+ }, 500)
+ }
+ }
+
+ const activeStep = dashboardSteps[currentStep]
+
+ return (
+ <>
+ {/* Main Container - hide when modal is open */}
+ {!isModalOpen && !isClosing && (
+
+ {/* Tour Selection Menu */}
+
+
Select Tour
+
+ {tourOptions.map((option) => (
+
+ ))}
+
+
+
+ {/* Main Preview */}
+
+
+

+
+
+
+
+
+
+ )}
+
+ {/* Modals for all tours */}
+ {(isModalOpen || isClosing) && selectedTour === 'guardpot' && (
+
+
+
+ )}
+
+ {(isModalOpen || isClosing) && selectedTour === 'attack-surface' && (
+
+ )}
+
+ {/* Modal - Dashboard */}
+ {isModalOpen && selectedTour === 'dashboard' && (
+ <>
+
+
e.stopPropagation()}>
+
+

+
+ {/* Dark overlay with highlight window - only show if not first step */}
+ {currentStep > 0 && (
+ <>
+ {/* Top overlay */}
+ {activeStep.region.topPct > 0 && (
+
+ )}
+
+ {/* Left overlay */}
+ {activeStep.region.leftPct > 0 && (
+
+ )}
+
+ {/* Right overlay */}
+ {activeStep.region.leftPct + activeStep.region.widthPct < 100 && (
+
+ )}
+
+ {/* Bottom overlay */}
+ {activeStep.region.topPct + activeStep.region.heightPct < 100 && (
+
+ )}
+
+ {/* Highlight border */}
+
+ >
+ )}
+
+ {/* Step info popup */}
+
+
+
+
+
+
+
+
{currentStep + 1} / {dashboardSteps.length}
+
{activeStep.title}
+
+
{activeStep.description}
+
+
+
+
+ {currentStep === dashboardSteps.length - 1 ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+ {/* Progress Bar – identical behavior to other tours */}
+ {!isModalAnimating && (
+
+ {dashboardSteps.map((step, index) => (
+
handleStepClick(index)}
+ title={step.title}
+ >
+
+
+ {index + 1}
+
+
+ ))}
+
+ )}
+ >
+ )}
+ >
+ )
+}
+
+
diff --git a/src/components/GuardpotTour.css b/src/components/GuardpotTour.css
new file mode 100644
index 0000000..395db29
--- /dev/null
+++ b/src/components/GuardpotTour.css
@@ -0,0 +1,656 @@
+/* GuardpotTour Component Styles */
+
+/* Main Preview Container */
+.guardpot-preview-container {
+ position: relative;
+ aspect-ratio: 16 / 9;
+ background-color: #0b0d10;
+ border-radius: 10px;
+ overflow: hidden;
+}
+
+.guardpot-preview-image {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ display: block;
+}
+
+.guardpot-preview-overlay {
+ position: absolute;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.7);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.guardpot-start-button {
+ padding: 16px 40px;
+ background-color: #b02f34;
+ color: white;
+ border: none;
+ border-radius: 12px;
+ font-size: 1.2rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 200ms ease;
+}
+
+.guardpot-start-button:hover {
+ background-color: #c0353a;
+ transform: translateY(-2px);
+ box-shadow: 0 4px 20px rgba(176, 47, 52, 0.3);
+}
+
+/* Modal */
+.guardpot-modal-backdrop {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: #0f0f12;
+ z-index: 1000;
+ animation: guardpotModalBackdropFadeIn 250ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+ overflow: hidden;
+}
+
+.guardpot-modal-backdrop.closing {
+ animation: guardpotModalBackdropFadeOut 250ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+}
+
+.guardpot-modal-content {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ background-color: #0b0d10;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+ padding: 20px;
+ animation: guardpotModalContentSlideIn 300ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+}
+
+.guardpot-modal-content.closing {
+ animation: guardpotModalContentSlideOut 250ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+}
+
+.guardpot-modal-image-wrapper {
+ position: relative;
+ max-width: 95vw;
+ max-height: 95vh;
+ width: fit-content;
+ height: fit-content;
+ margin: 0 auto;
+}
+
+.guardpot-modal-image {
+ width: 100%;
+ height: 100%;
+ max-width: 100%;
+ max-height: 100%;
+ object-fit: contain;
+ display: block;
+}
+
+/* Overlay System */
+.guardpot-overlay-top {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ background: rgba(0, 0, 0, 0.85);
+ pointer-events: none;
+ transition: all 500ms ease-in-out;
+ opacity: 1;
+}
+
+.guardpot-overlay-left {
+ position: absolute;
+ left: 0;
+ background: rgba(0, 0, 0, 0.85);
+ pointer-events: none;
+ transition: all 500ms ease-in-out;
+ opacity: 1;
+}
+
+.guardpot-overlay-right {
+ position: absolute;
+ right: 0;
+ background: rgba(0, 0, 0, 0.85);
+ pointer-events: none;
+ transition: all 500ms ease-in-out;
+ opacity: 1;
+}
+
+.guardpot-overlay-bottom {
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.85);
+ pointer-events: none;
+ transition: all 500ms ease-in-out;
+ opacity: 1;
+}
+
+.guardpot-overlay-transitioning {
+ opacity: 0.5;
+}
+
+/* Highlight Border */
+.guardpot-highlight-border {
+ position: absolute;
+ border: 2px solid #b02f34;
+ border-radius: 10px;
+ pointer-events: none;
+ transition: all 500ms ease-in-out;
+ box-shadow: 0 0 0 2px rgba(176, 47, 52, 0.3), 0 0 15px rgba(176, 47, 52, 0.2);
+ opacity: 1;
+ transform: scale(1);
+}
+
+.guardpot-highlight-border.transitioning {
+ opacity: 0.5;
+ transform: scale(0.98);
+}
+
+/* Step Info Popup */
+.guardpot-step-info-popup {
+ position: absolute;
+ background-color: #111319;
+ border: 1px solid #262a33;
+ border-radius: 12px;
+ padding: 20px;
+ color: #f1f2f5;
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(176, 47, 52, 0.3);
+ min-width: 300px;
+ max-width: 400px;
+ transition: all 500ms cubic-bezier(0.4, 0, 0.2, 1);
+ opacity: 1;
+ transform: translateY(0) scale(1);
+}
+
+.guardpot-step-info-popup.transitioning {
+ opacity: 0.7;
+ transform: translateY(-10px) scale(0.95);
+}
+
+.guardpot-step-info-popup.closing {
+ opacity: 0;
+ transform: translateY(20px) scale(0.8);
+}
+
+/* Popup Animated Border */
+.guardpot-popup-animated-border {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ border-radius: 12px;
+ border: 2px solid transparent;
+ background: linear-gradient(45deg, rgba(176, 47, 52, 0.3), rgba(176, 47, 52, 0.1));
+ background-clip: border-box;
+ animation: guardpotPulseGlow 2s infinite;
+ pointer-events: none;
+}
+
+/* Quit Button */
+.guardpot-quit-button {
+ position: absolute;
+ top: 12px;
+ right: 12px;
+ width: 28px;
+ height: 28px;
+ background: rgba(0, 0, 0, 0.5);
+ border: 1px solid #262a33;
+ border-radius: 50%;
+ color: #b9bfca;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 16px;
+ font-weight: normal;
+ line-height: 1;
+ z-index: 20;
+ transition: all 200ms ease;
+}
+
+.guardpot-quit-button:hover {
+ background: rgba(176, 47, 52, 0.8);
+ color: white;
+ transform: scale(1.1);
+}
+
+/* Popup Content */
+.guardpot-popup-content {
+ position: relative;
+ z-index: 10;
+}
+
+.guardpot-popup-header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 12px;
+}
+
+.guardpot-step-counter {
+ background-color: #b02f34;
+ color: white;
+ font-size: 12px;
+ padding: 4px 8px;
+ border-radius: 999px;
+ font-weight: 600;
+}
+
+.guardpot-popup-title {
+ margin: 0;
+ font-weight: 600;
+ font-size: 1.1rem;
+}
+
+.guardpot-popup-description {
+ margin: 0 0 16px 0;
+ color: #b9bfca;
+ line-height: 1.5;
+}
+
+.guardpot-popup-navigation {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.guardpot-nav-button {
+ padding: 8px 16px;
+ border-radius: 8px;
+ border: 1px solid #2a2f39;
+ background-color: #171a21;
+ color: #f1f2f5;
+ cursor: pointer;
+ font-size: 14px;
+ transition: all 200ms ease;
+}
+
+.guardpot-nav-button:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.guardpot-nav-button:hover:not(:disabled) {
+ background-color: #1a1d25;
+ border-color: #3a3f49;
+}
+
+.guardpot-nav-button.primary {
+ border-color: #8e262a;
+ background-color: #b02f34;
+ color: white;
+}
+
+.guardpot-nav-button.primary:hover {
+ background-color: #c0353a;
+}
+
+.guardpot-step-dots-container {
+ display: flex;
+ gap: 6px;
+}
+
+.guardpot-step-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background-color: #2a2f39;
+ border: none;
+ cursor: pointer;
+ transition: all 300ms ease;
+ transform: scale(1);
+}
+
+.guardpot-step-dot.active {
+ background-color: #b02f34;
+ transform: scale(1.2);
+ box-shadow: 0 0 8px rgba(176, 47, 52, 0.4);
+}
+
+.guardpot-step-dot:hover:not(.active) {
+ background-color: #3a3f49;
+}
+
+/* Progress Bar */
+.guardpot-tour-progress-bar {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 8px;
+ background-color: rgba(0, 0, 0, 0.3);
+ display: flex;
+ gap: 2px;
+ z-index: 1001;
+ backdrop-filter: blur(10px);
+ animation: guardpotProgressBarFadeIn 250ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+}
+
+.guardpot-tour-progress-bar.closing {
+ animation: guardpotProgressBarFadeOut 250ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+}
+
+.guardpot-progress-segment {
+ flex: 1;
+ position: relative;
+ cursor: pointer;
+ overflow: visible;
+ transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
+ background-color: rgba(255, 255, 255, 0.1);
+}
+
+.guardpot-progress-segment:hover {
+ background-color: rgba(255, 255, 255, 0.15);
+}
+
+.guardpot-progress-segment-fill {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(90deg, #b02f34 0%, #d24449 100%);
+ transform: scaleX(0);
+ transform-origin: left;
+ transition: transform 500ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.guardpot-progress-segment.completed .guardpot-progress-segment-fill {
+ transform: scaleX(1);
+}
+
+.guardpot-progress-segment.active {
+ background-color: rgba(176, 47, 52, 0.2);
+}
+
+.guardpot-progress-segment.active .guardpot-progress-segment-fill {
+ transform: scaleX(1);
+ animation: guardpotProgressPulse 2s infinite;
+}
+
+.guardpot-progress-step-number {
+ position: absolute;
+ bottom: 12px;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ background-color: #2a2f39;
+ border: 2px solid #262a33;
+ color: #b9bfca;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 14px;
+ font-weight: 600;
+ transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
+ z-index: 10;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
+}
+
+.guardpot-progress-segment:hover .guardpot-progress-step-number {
+ transform: translateX(-50%) scale(1.1);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
+}
+
+.guardpot-progress-step-number.completed {
+ background-color: #b02f34;
+ border-color: #8e262a;
+ color: white;
+ box-shadow: 0 2px 8px rgba(176, 47, 52, 0.4);
+}
+
+.guardpot-progress-step-number.active {
+ background: linear-gradient(135deg, #b02f34 0%, #d24449 100%);
+ border-color: #d24449;
+ color: white;
+ box-shadow: 0 4px 16px rgba(176, 47, 52, 0.6);
+ animation: guardpotStepNumberPulse 2s infinite;
+}
+
+@keyframes guardpotProgressPulse {
+ 0%, 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.7;
+ }
+}
+
+@keyframes guardpotStepNumberPulse {
+ 0%, 100% {
+ transform: translateX(-50%) scale(1);
+ box-shadow: 0 4px 16px rgba(176, 47, 52, 0.6);
+ }
+ 50% {
+ transform: translateX(-50%) scale(1.05);
+ box-shadow: 0 6px 20px rgba(176, 47, 52, 0.8);
+ }
+}
+
+@keyframes guardpotProgressBarFadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes guardpotProgressBarFadeOut {
+ from {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ to {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+}
+
+/* Animations */
+@keyframes guardpotPulseGlow {
+ 0% { box-shadow: 0 0 0 0 rgba(176, 47, 52, 0.4); }
+ 50% { box-shadow: 0 0 8px 4px rgba(176, 47, 52, 0.6); }
+ 100% { box-shadow: 0 0 0 0 rgba(176, 47, 52, 0.4); }
+}
+
+@keyframes guardpotModalBackdropFadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes guardpotModalBackdropFadeOut {
+ from {
+ opacity: 1;
+ }
+ to {
+ opacity: 0;
+ }
+}
+
+@keyframes guardpotModalContentSlideIn {
+ from {
+ opacity: 0;
+ transform: scale(0.95);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+@keyframes guardpotModalContentSlideOut {
+ from {
+ opacity: 1;
+ transform: scale(1);
+ }
+ to {
+ opacity: 0;
+ transform: scale(0.95);
+ }
+}
+
+/* Responsive Design */
+@media (max-width: 1024px) {
+ .guardpot-preview-container {
+ aspect-ratio: 4 / 3;
+ }
+
+ .guardpot-start-button {
+ padding: 14px 36px;
+ font-size: 1.1rem;
+ }
+}
+
+@media (max-width: 768px) {
+ .guardpot-preview-container {
+ aspect-ratio: 3 / 2;
+ }
+
+ .guardpot-start-button {
+ padding: 12px 32px;
+ font-size: 1rem;
+ }
+
+ .guardpot-step-info-popup {
+ min-width: 280px;
+ max-width: 320px;
+ padding: 16px;
+ }
+
+ .guardpot-popup-navigation {
+ flex-wrap: wrap;
+ gap: 8px;
+ }
+
+ .guardpot-step-dots-container {
+ order: -1;
+ width: 100%;
+ justify-content: center;
+ }
+}
+
+/* Guardpot Step Popup - Animated */
+.guardpot-step-popup {
+ position: absolute;
+ background-color: #111319;
+ border: 1px solid #262a33;
+ border-radius: 12px;
+ padding: 20px;
+ color: #f1f2f5;
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(176, 47, 52, 0.3);
+ min-width: 300px;
+ max-width: 400px;
+ animation: guardpotPopupSlideIn 600ms ease-out forwards;
+ transition: all 500ms ease-in-out;
+}
+
+.guardpot-step-popup.transitioning {
+ opacity: 0.3;
+ transition: all 500ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
+}
+
+.guardpot-step-popup.transitioning.transition-next {
+ transform: translateX(80px) scale(0.95);
+}
+
+.guardpot-step-popup.transitioning.transition-prev {
+ transform: translateX(-80px) scale(0.95);
+}
+
+.guardpot-step-popup.closing {
+ opacity: 0;
+ transform: translateY(20px) scale(0.8);
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+/* Popup Animated Border */
+.guardpot-popup-animated-border {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ border-radius: 12px;
+ border: 2px solid transparent;
+ background: linear-gradient(45deg, rgba(176, 47, 52, 0.3), rgba(176, 47, 52, 0.1));
+ background-clip: border-box;
+ animation: pulseGlow 2s infinite;
+ transition: all 400ms ease-in-out;
+ pointer-events: none;
+}
+
+/* Quit Button */
+.guardpot-quit-button {
+ position: absolute;
+ top: 12px;
+ right: 12px;
+ width: 28px;
+ height: 28px;
+ background: rgba(0, 0, 0, 0.5);
+ border: 1px solid #262a33;
+ border-radius: 50%;
+ color: #b9bfca;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 16px;
+ font-weight: normal;
+ line-height: 1;
+ z-index: 20;
+ transition: all 200ms ease;
+}
+
+.guardpot-quit-button:hover {
+ background: rgba(176, 47, 52, 0.8);
+ color: white;
+ transform: scale(1.1);
+}
+
+/* Popup Content */
+.guardpot-popup-content {
+ position: relative;
+ z-index: 10;
+ transition: all 400ms ease-in-out;
+}
+
+/* Animations */
+@keyframes guardpotPopupSlideIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px) scale(0.9);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ }
+}
+
+@keyframes pulseGlow {
+ 0% { box-shadow: 0 0 0 0 rgba(176, 47, 52, 0.4); }
+ 50% { box-shadow: 0 0 8px 4px rgba(176, 47, 52, 0.6); }
+ 100% { box-shadow: 0 0 0 0 rgba(176, 47, 52, 0.4); }
+}
+
diff --git a/src/components/GuardpotTour.tsx b/src/components/GuardpotTour.tsx
new file mode 100644
index 0000000..306c2ad
--- /dev/null
+++ b/src/components/GuardpotTour.tsx
@@ -0,0 +1,339 @@
+import { useState } from 'react'
+import guardpotImg from '../assets/guardpot.jpeg'
+import './GuardpotTour.css'
+
+type TourStep = {
+ id: string
+ title: string
+ description: string
+ region: { leftPct: number; topPct: number; widthPct: number; heightPct: number }
+ popupPosition?: { leftPct?: number; topPct?: number; rightPct?: number; bottomPct?: number }
+}
+
+const guardpotSteps: TourStep[] = [
+ {
+ id: 'overview',
+ title: 'Guardpot Overview',
+ description: 'Guardpot yönetim sayfasına hoş geldiniz! Bu sayfada tüm Guardpot agentlarınızı görüntüleyebilir, yönetebilir ve yeni agentlar ekleyebilirsiniz.',
+ region: { leftPct: 0, topPct: 0, widthPct: 100, heightPct: 100 },
+ popupPosition: { rightPct: 35, topPct: 55 },
+ },
+ {
+ id: 'guardpot-card',
+ title: 'Guardpot-Local Card',
+ description: 'Bu kart mevcut Guardpot\'unuzun detaylarını gösterir. IP adresi, işletim sistemi ve son aktivite bilgilerini burada görebilirsiniz.',
+ region: { leftPct: 15, topPct: 3, widthPct: 41, heightPct: 20 },
+ popupPosition: { rightPct: 61, topPct: 24 },
+ },
+ {
+ id: 'action-buttons',
+ title: 'Action Buttons',
+ description: 'Bu butonlar ile yeni Guardpot ekleyebilir, kurulum kılavuzunu görüntüleyebilir ve Guardpot havuzunu yönetebilirsiniz.',
+ region: { leftPct: 58, topPct: 3, widthPct: 40, heightPct: 21 },
+ popupPosition: { leftPct: 75, topPct: 25 },
+ },
+ {
+ id: 'guardpot-list',
+ title: 'Guardpot List',
+ description: 'Tüm Guardpot agentlarınızın listesi burada görüntülenir. Durum, konum, IP adresi ve işlem butonları ile agentlarınızı yönetebilirsiniz.',
+ region: { leftPct: 15, topPct: 25, widthPct: 83, heightPct: 60 },
+ popupPosition: { leftPct: 17, bottomPct: 18 },
+ },
+]
+
+type GuardpotTourProps = {
+ autoStart?: boolean
+ onClose?: () => void
+}
+
+export default function GuardpotTour({ autoStart = false, onClose }: GuardpotTourProps) {
+ const [isModalOpen, setIsModalOpen] = useState(autoStart)
+ const [currentStep, setCurrentStep] = useState(0)
+ const [isTransitioning, setIsTransitioning] = useState(false)
+ const [isModalAnimating, setIsModalAnimating] = useState(false)
+ const [transitionDirection, setTransitionDirection] = useState<'next' | 'prev'>('next')
+
+ const handleStartTour = () => {
+ setIsModalOpen(true)
+ setCurrentStep(0)
+ }
+
+ const handleClose = () => {
+ setIsModalAnimating(true)
+ // Parent'ı hemen bilgilendir
+ if (onClose) {
+ onClose()
+ }
+ setTimeout(() => {
+ setIsModalOpen(false)
+ setCurrentStep(0)
+ setIsTransitioning(false)
+ setTimeout(() => {
+ setIsModalAnimating(false)
+ }, 50)
+ }, 250)
+ }
+
+ const handleNext = () => {
+ if (currentStep < guardpotSteps.length - 1) {
+ setTransitionDirection('next')
+ setIsTransitioning(true)
+ setTimeout(() => {
+ setCurrentStep(currentStep + 1)
+ setIsTransitioning(false)
+ }, 500)
+ }
+ }
+
+ const handlePrevious = () => {
+ if (currentStep > 0) {
+ setTransitionDirection('prev')
+ setIsTransitioning(true)
+ setTimeout(() => {
+ setCurrentStep(currentStep - 1)
+ setIsTransitioning(false)
+ }, 500)
+ }
+ }
+
+ const handleStepClick = (stepIndex: number) => {
+ if (stepIndex !== currentStep) {
+ setTransitionDirection(stepIndex > currentStep ? 'next' : 'prev')
+ setIsTransitioning(true)
+ setTimeout(() => {
+ setCurrentStep(stepIndex)
+ setIsTransitioning(false)
+ }, 500)
+ }
+ }
+
+ const activeStep = guardpotSteps[currentStep]
+
+ return (
+ <>
+ {/* Main Preview - only show if not autoStart */}
+ {!autoStart && (
+
+

+
+
+
+
+
+ )}
+
+ {/* Modal */}
+ {isModalOpen && (
+ <>
+
+
e.stopPropagation()}
+ >
+
+

+
+ {/* Dark overlay with highlight window - only show if not first step */}
+ {currentStep > 0 && (
+ <>
+ {/* Top overlay */}
+ {activeStep.region.topPct > 0 && (
+
+ )}
+
+ {/* Left overlay */}
+ {activeStep.region.leftPct > 0 && (
+
+ )}
+
+ {/* Right overlay */}
+ {activeStep.region.leftPct + activeStep.region.widthPct < 100 && (
+
+ )}
+
+ {/* Bottom overlay */}
+ {activeStep.region.topPct + activeStep.region.heightPct < 100 && (
+
+ )}
+
+ {/* Highlight border */}
+
+ >
+ )}
+
+ {/* Step info popup */}
+
+ {/* Subtle animated border */}
+
+
+ {/* Quit button */}
+
+
+ {/* Content with higher z-index */}
+
+
+
+ {currentStep + 1} / {guardpotSteps.length}
+
+
+ {activeStep.title}
+
+
+
+ {activeStep.description}
+
+
+
+
+
+ {currentStep === guardpotSteps.length - 1 ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+ {/* Progress Bar - only show when modal is open */}
+ {!isModalAnimating && (
+
+ {guardpotSteps.map((step, index) => (
+
handleStepClick(index)}
+ title={step.title}
+ >
+
+
+ {index + 1}
+
+
+ ))}
+
+ )}
+ >
+ )}
+
+ {/* CSS Animations */}
+
+ >
+ )
+}
diff --git a/src/components/OnboardingTour.tsx b/src/components/OnboardingTour.tsx
new file mode 100644
index 0000000..c9cf8f0
--- /dev/null
+++ b/src/components/OnboardingTour.tsx
@@ -0,0 +1,215 @@
+import { useMemo, useRef, useState } from 'react';
+
+export type Step = {
+ id: string;
+ title: string;
+ description: string;
+ // percentage-based hotspot region over the base image (left, top, width, height)
+ region: { leftPct: number; topPct: number; widthPct: number; heightPct: number };
+ media?: { type: 'image' | 'video'; src: string; alt?: string };
+};
+
+type OnboardingTourProps = {
+ imageSrc: string;
+ steps: Step[];
+ editable?: boolean;
+ onStepsChange?: (steps: Step[]) => void;
+};
+
+const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);
+
+export default function OnboardingTour({ imageSrc, steps, editable = false, onStepsChange }: OnboardingTourProps) {
+ const [activeIndex, setActiveIndex] = useState(0);
+ const containerRef = useRef(null);
+
+ const activeStep = steps[activeIndex];
+
+ const handlePrev = () => setActiveIndex((i) => clamp(i - 1, 0, steps.length - 1));
+ const handleNext = () => setActiveIndex((i) => clamp(i + 1, 0, steps.length - 1));
+ const handleSkip = () => setActiveIndex(steps.length - 1);
+
+ const progressPct = useMemo(() => ((activeIndex + 1) / steps.length) * 100, [activeIndex, steps.length]);
+
+ const updateRegion = (delta: Partial) => {
+ if (!editable) return;
+ const next = steps.map((s, i) => {
+ if (i !== activeIndex) return s;
+ const r = s.region;
+ const newRegion = {
+ leftPct: clamp((delta.leftPct ?? r.leftPct), 0, 100),
+ topPct: clamp((delta.topPct ?? r.topPct), 0, 100),
+ widthPct: clamp((delta.widthPct ?? r.widthPct), 1, 100),
+ heightPct: clamp((delta.heightPct ?? r.heightPct), 1, 100),
+ };
+ return { ...s, region: newRegion };
+ });
+ onStepsChange?.(next);
+ };
+
+ const nudge = (dx = 0, dy = 0) => {
+ const r = activeStep.region;
+ updateRegion({ leftPct: clamp(r.leftPct + dx, 0, 100), topPct: clamp(r.topPct + dy, 0, 100) });
+ };
+ const resize = (dw = 0, dh = 0) => {
+ const r = activeStep.region;
+ updateRegion({ widthPct: clamp(r.widthPct + dw, 1, 100), heightPct: clamp(r.heightPct + dh, 1, 100) });
+ };
+
+ const POP_W = 24; // approximate popover width in percent of canvas
+ const POP_H = 22; // approximate popover height in percent of canvas
+ const GAP = 2; // gap between region and popover in percent
+
+ const computePopoverPlacement = () => {
+ const r = activeStep.region;
+ const spaceBelow = 100 - (r.topPct + r.heightPct);
+ const spaceAbove = r.topPct;
+ const spaceRight = 100 - (r.leftPct + r.widthPct);
+ // const spaceLeft = r.leftPct; // not needed currently
+
+ // Special case: Step 5 (globe) must always be to the LEFT, stuck to region's left side
+ if (activeStep.id === 'globe') {
+ // Shift a bit more to the left to sit clearer outside of the region
+ const left = clamp(r.leftPct - POP_W - 3, 2, 100 - POP_W - 2);
+ const top = clamp(r.topPct, 2, 100 - POP_H - 2);
+ return { left, top, cls: '' };
+ }
+
+ // Prefer bottom → top → right → left, never overlap the region and never leave the canvas
+ if (spaceBelow >= POP_H) {
+ return {
+ left: clamp(r.leftPct, 2, 100 - POP_W - 2),
+ top: r.topPct + r.heightPct + GAP,
+ cls: '',
+ };
+ }
+ if (spaceAbove >= POP_H) {
+ return {
+ left: clamp(r.leftPct, 2, 100 - POP_W - 2),
+ top: clamp(r.topPct - POP_H - GAP, 2, 100 - POP_H - 2),
+ cls: '',
+ };
+ }
+ if (spaceRight >= POP_W) {
+ return {
+ left: r.leftPct + r.widthPct + GAP,
+ top: clamp(r.topPct, 2, 100 - POP_H - 2),
+ cls: 'side-right',
+ };
+ }
+ // fallback: place to left
+ const left = clamp(r.leftPct - POP_W - GAP, 2, 100 - POP_W - 2);
+ return {
+ left,
+ top: clamp(r.topPct, 2, 100 - POP_H - 2),
+ cls: 'side-left',
+ };
+ };
+
+ return (
+
+
+

+
+ {steps.map((step, index) => {
+ const isActive = index === activeIndex;
+ return (
+
+
+
+
+
+ {steps.map((s, i) => (
+ setActiveIndex(i)} aria-label={`Step ${i + 1}`} />
+ ))}
+
+
+ );
+}
+
+
diff --git a/src/index.css b/src/index.css
index 08a3ac9..f924c25 100644
--- a/src/index.css
+++ b/src/index.css
@@ -3,14 +3,19 @@
line-height: 1.5;
font-weight: 400;
- color-scheme: light dark;
+ color-scheme: dark;
color: rgba(255, 255, 255, 0.87);
- background-color: #242424;
+ background-color: #0f0f12;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
+ overflow: hidden;
+}
+
+html {
+ overflow: hidden;
}
a {
@@ -24,10 +29,21 @@ a:hover {
body {
margin: 0;
- display: flex;
- place-items: center;
min-width: 320px;
min-height: 100vh;
+ background-color: #0f0f12;
+ color: rgba(255, 255, 255, 0.87);
+ overflow: hidden;
+}
+
+/* Hide scrollbar for all browsers */
+* {
+ scrollbar-width: none; /* Firefox */
+ -ms-overflow-style: none; /* IE and Edge */
+}
+
+*::-webkit-scrollbar {
+ display: none; /* Chrome, Safari and Opera */
}
h1 {
@@ -54,15 +70,4 @@ button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
-@media (prefers-color-scheme: light) {
- :root {
- color: #213547;
- background-color: #ffffff;
- }
- a:hover {
- color: #747bff;
- }
- button {
- background-color: #f9f9f9;
- }
-}
+/* Light mode styles removed - using dark theme only */