Merge pull request #53 from davidmonterocrespo24/website
feat: add GitHub star banner to encourage users to star the project a…pull/74/head
commit
400437278f
|
|
@ -0,0 +1,87 @@
|
|||
.gh-star-banner {
|
||||
position: fixed;
|
||||
bottom: 28px;
|
||||
right: 28px;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 14px;
|
||||
background: #1e2228;
|
||||
border: 1px solid #3c4049;
|
||||
border-radius: 12px;
|
||||
padding: 16px 18px;
|
||||
width: 320px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.55);
|
||||
animation: gh-banner-in 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
@keyframes gh-banner-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px) scale(0.97);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.gh-star-banner__icon {
|
||||
color: #e3b341;
|
||||
flex-shrink: 0;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.gh-star-banner__body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.gh-star-banner__body strong {
|
||||
display: block;
|
||||
color: #e1e4e8;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.gh-star-banner__body p {
|
||||
color: #8b949e;
|
||||
font-size: 12.5px;
|
||||
line-height: 1.45;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
.gh-star-banner__btn {
|
||||
display: inline-block;
|
||||
background: #e3b341;
|
||||
color: #1b1f23;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
padding: 6px 14px;
|
||||
border-radius: 6px;
|
||||
text-decoration: none;
|
||||
transition: background 0.15s ease;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.gh-star-banner__btn:hover {
|
||||
background: #f0c860;
|
||||
}
|
||||
|
||||
.gh-star-banner__close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #6e7681;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
margin-top: 1px;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
|
||||
.gh-star-banner__close:hover {
|
||||
color: #e1e4e8;
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import React from 'react';
|
||||
import './GitHubStarBanner.css';
|
||||
|
||||
const GITHUB_URL = 'https://github.com/davidmonterocrespo24/velxio';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const GitHubStarBanner: React.FC<Props> = ({ onClose }) => (
|
||||
<div className="gh-star-banner" role="dialog" aria-label="Star Velxio on GitHub">
|
||||
<div className="gh-star-banner__icon">
|
||||
{/* GitHub logo */}
|
||||
<svg width="30" height="30" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="gh-star-banner__body">
|
||||
<strong>Enjoying Velxio?</strong>
|
||||
<p>
|
||||
A ⭐ on GitHub helps more developers discover the project — it only takes 2 seconds!
|
||||
</p>
|
||||
<a
|
||||
href={GITHUB_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="gh-star-banner__btn"
|
||||
onClick={onClose}
|
||||
>
|
||||
⭐ Star on GitHub
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<button className="gh-star-banner__close" onClick={onClose} aria-label="Dismiss">
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -20,6 +20,7 @@ import { Oscilloscope } from '../components/simulator/Oscilloscope';
|
|||
import { AppHeader } from '../components/layout/AppHeader';
|
||||
import { SaveProjectModal } from '../components/layout/SaveProjectModal';
|
||||
import { LoginPromptModal } from '../components/layout/LoginPromptModal';
|
||||
import { GitHubStarBanner } from '../components/layout/GitHubStarBanner';
|
||||
import { useSimulatorStore } from '../store/useSimulatorStore';
|
||||
import { useOscilloscopeStore } from '../store/useOscilloscopeStore';
|
||||
import { useAuthStore } from '../store/useAuthStore';
|
||||
|
|
@ -68,6 +69,46 @@ export const EditorPage: React.FC = () => {
|
|||
const [bottomPanelHeight, setBottomPanelHeight] = useState(BOTTOM_PANEL_DEFAULT);
|
||||
const [saveModalOpen, setSaveModalOpen] = useState(false);
|
||||
const [loginPromptOpen, setLoginPromptOpen] = useState(false);
|
||||
const [showStarBanner, setShowStarBanner] = useState(false);
|
||||
|
||||
// ── GitHub star prompt (show once: 2nd visit OR after 3 min) ──────────────
|
||||
useEffect(() => {
|
||||
const STAR_KEY = 'velxio_star_prompted';
|
||||
const VISITS_KEY = 'velxio_editor_visits';
|
||||
const FIRST_VISIT_KEY = 'velxio_editor_first_visit';
|
||||
const THREE_MIN = 3 * 60 * 1000;
|
||||
|
||||
if (localStorage.getItem(STAR_KEY)) return;
|
||||
|
||||
// Increment visit counter
|
||||
const visits = parseInt(localStorage.getItem(VISITS_KEY) ?? '0', 10) + 1;
|
||||
localStorage.setItem(VISITS_KEY, String(visits));
|
||||
|
||||
// Record timestamp of first visit
|
||||
if (!localStorage.getItem(FIRST_VISIT_KEY)) {
|
||||
localStorage.setItem(FIRST_VISIT_KEY, String(Date.now()));
|
||||
}
|
||||
const firstVisit = parseInt(localStorage.getItem(FIRST_VISIT_KEY)!, 10);
|
||||
|
||||
// Show immediately on second+ visit
|
||||
if (visits >= 2) {
|
||||
setShowStarBanner(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise schedule after the 3-minute mark
|
||||
const elapsed = Date.now() - firstVisit;
|
||||
const delay = Math.max(0, THREE_MIN - elapsed);
|
||||
const timer = setTimeout(() => {
|
||||
if (!localStorage.getItem(STAR_KEY)) setShowStarBanner(true);
|
||||
}, delay);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
const handleDismissStarBanner = () => {
|
||||
localStorage.setItem('velxio_star_prompted', '1');
|
||||
setShowStarBanner(false);
|
||||
};
|
||||
const [explorerOpen, setExplorerOpen] = useState(true);
|
||||
const [explorerWidth, setExplorerWidth] = useState(EXPLORER_DEFAULT);
|
||||
const [isMobile, setIsMobile] = useState(() => window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT}px)`).matches);
|
||||
|
|
@ -348,6 +389,7 @@ export const EditorPage: React.FC = () => {
|
|||
|
||||
{saveModalOpen && <SaveProjectModal onClose={() => setSaveModalOpen(false)} />}
|
||||
{loginPromptOpen && <LoginPromptModal onClose={() => setLoginPromptOpen(false)} />}
|
||||
{showStarBanner && <GitHubStarBanner onClose={handleDismissStarBanner} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -450,11 +450,13 @@ export const LandingPage: React.FC = () => {
|
|||
<section className="landing-hero">
|
||||
<div className="hero-left">
|
||||
<h1 className="hero-title">
|
||||
Emulate Hardware.<br />
|
||||
<span className="hero-accent">In your browser.</span>
|
||||
Simulate Arduino,<br />
|
||||
ESP32 & Raspberry Pi.<br />
|
||||
<span className="hero-accent">And 16 more boards in your browser..</span>
|
||||
</h1>
|
||||
<p className="hero-subtitle">
|
||||
Write, compile, and simulate 17+ boards across 5 CPU architectures — no hardware, no cloud, no limits. Real emulation running entirely on your machine.
|
||||
Write code, compile, and run on 19 real boards — Arduino Uno, ESP32, ESP32-C3,
|
||||
Raspberry Pi Pico, Raspberry Pi 3, and more. No hardware, no cloud, no limits.
|
||||
</p>
|
||||
<div className="hero-ctas">
|
||||
<Link to="/editor" className="cta-primary">
|
||||
|
|
@ -479,7 +481,7 @@ export const LandingPage: React.FC = () => {
|
|||
<div className="section-header">
|
||||
<span className="section-label">Supported Hardware</span>
|
||||
<h2 className="section-title">Every architecture.<br />One tool.</h2>
|
||||
<p className="section-sub">17 boards across 5 CPU architectures — AVR, ARM, RISC-V, Xtensa, and Linux. All running locally, no cloud needed.</p>
|
||||
<p className="section-sub">19 boards across 5 CPU architectures — AVR8, ARM Cortex-M0+, RISC-V, Xtensa, and Linux. All running locally, no cloud needed.</p>
|
||||
</div>
|
||||
|
||||
{/* ── AVR8 · avr8js ────────────────────────────────────────── */}
|
||||
|
|
|
|||
Loading…
Reference in New Issue