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 logo - - - React logo - -
-

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 && ( +
+ Attack Surface Analysis + +
+ +
+
+ )} + + {/* Modal */} + {isModalOpen && ( + <> +
+
e.stopPropagation()} + > +
+ Attack Surface Analysis + + {/* 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()}> +
+ Guardpot Dashboard + + {/* 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 && ( +
+ Guardpot Management + +
+ +
+
+ )} + + {/* Modal */} + {isModalOpen && ( + <> +
+
e.stopPropagation()} + > +
+ Guardpot Management + + {/* 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 ( +
+
+ Dashboard overview + + {steps.map((step, index) => { + const isActive = index === activeIndex; + return ( + +
+
+ + +
+
+ +
+
+ + + + +
+
{JSON.stringify(activeStep.region, null, 2)}
+
+ ) : null} +
+ +
+ + +
+
+ ); + })()} +
+ +
+
+
+ +
+ {steps.map((s, i) => ( +
+
+ ); +} + + 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 */