mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat(landing): add dismissible 2.0 launch banner
Deep otter-brown Web 1.0 style bar pinned above the fixed navbar (offset via --lb-h), with a localStorage-remembered dismissal and a render-blocking check so returning visitors see no flash. Temporary; remove once 2.0 is old news.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
---
|
||||
// TEMP: 2.0 launch banner. Remove a few weeks after release, once it's old news.
|
||||
// Dismissal is remembered in localStorage; a render-blocking check in Base.astro's
|
||||
// <head> sets data-lb-dismissed before paint so returning visitors see no flash.
|
||||
const RELEASE = "https://github.com/snapotter-hq/SnapOtter/releases/tag/v2.0.0";
|
||||
---
|
||||
|
||||
<aside class="lb-root" data-lb>
|
||||
<div class="lb-burst" aria-hidden="true">NEW!</div>
|
||||
<div class="lb-marquee" aria-hidden="true">
|
||||
<div class="lb-track">
|
||||
<span>★ SnapOtter <b>2.0</b> is HERE ★ SnapOtter <b>2.0</b> is HERE ★ SnapOtter <b>2.0</b> is HERE ★ SnapOtter <b>2.0</b> is HERE ★ SnapOtter <b>2.0</b> is HERE ★ </span>
|
||||
<span>★ SnapOtter <b>2.0</b> is HERE ★ SnapOtter <b>2.0</b> is HERE ★ SnapOtter <b>2.0</b> is HERE ★ SnapOtter <b>2.0</b> is HERE ★ SnapOtter <b>2.0</b> is HERE ★ </span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="lb-btn" href={RELEASE} aria-label="See the SnapOtter 2.0 release notes">Click here!</a>
|
||||
<button class="lb-x" type="button" data-lb-close aria-label="Dismiss announcement">✕</button>
|
||||
</aside>
|
||||
|
||||
<style is:global>
|
||||
:root { --lb-h: 48px; }
|
||||
html[data-lb-dismissed] { --lb-h: 0px; }
|
||||
|
||||
/* Push the fixed navbar and all page content down by the banner height, so
|
||||
the banner sits above the nav without overlapping. Collapses when dismissed. */
|
||||
body { padding-top: var(--lb-h); transition: padding-top 0.28s ease; }
|
||||
nav.fixed { top: var(--lb-h); transition: top 0.28s ease; }
|
||||
|
||||
.lb-root {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 60;
|
||||
height: var(--lb-h);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 0 42px 0 10px;
|
||||
background: #1a1210;
|
||||
background-image: repeating-linear-gradient(45deg, rgba(240, 149, 80, 0.16) 0 8px, transparent 8px 16px);
|
||||
border-top: 3px ridge #f0a24e;
|
||||
border-bottom: 3px solid #0b0704;
|
||||
box-shadow: 0 6px 18px -10px rgba(0, 0, 0, 0.5);
|
||||
font-family: "Comic Sans MS", "Trebuchet MS", var(--font-body, sans-serif);
|
||||
opacity: 1;
|
||||
transition: opacity 0.22s ease;
|
||||
}
|
||||
html[data-lb-dismissed] .lb-root { opacity: 0; visibility: hidden; }
|
||||
|
||||
.lb-burst {
|
||||
flex: 0 0 auto;
|
||||
color: #7a1503;
|
||||
font-weight: 900;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.02em;
|
||||
background: #ffd34e;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
text-align: center;
|
||||
clip-path: polygon(50% 0, 61% 12%, 77% 6%, 78% 23%, 95% 25%, 86% 40%, 100% 50%, 86% 60%, 95% 75%, 78% 77%, 77% 94%, 61% 88%, 50% 100%, 39% 88%, 23% 94%, 22% 77%, 5% 75%, 14% 60%, 0 50%, 14% 40%, 5% 25%, 22% 23%, 23% 6%, 39% 12%);
|
||||
animation: lb-spin 7s linear infinite;
|
||||
box-shadow: 0 0 0 2px #b5471b;
|
||||
}
|
||||
.lb-marquee { flex: 1 1 auto; overflow: hidden; white-space: nowrap; }
|
||||
.lb-track { display: inline-flex; animation: lb-slide 22s linear infinite; will-change: transform; }
|
||||
.lb-track span { color: #fff6da; font-weight: 700; font-size: 15px; text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.55); }
|
||||
.lb-track b { color: #ffe86b; }
|
||||
|
||||
.lb-btn {
|
||||
flex: 0 0 auto;
|
||||
font-weight: 800;
|
||||
font-size: 12.5px;
|
||||
color: #12233b;
|
||||
background: linear-gradient(#fff, #cfd8e6);
|
||||
border: 2px outset #eef;
|
||||
padding: 5px 11px;
|
||||
border-radius: 3px;
|
||||
text-decoration: none;
|
||||
animation: lb-blink 1.1s steps(2, start) infinite;
|
||||
}
|
||||
.lb-btn:hover { background: linear-gradient(#fff, #dde6f2); }
|
||||
|
||||
.lb-x {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
font-weight: 900;
|
||||
font-size: 12px;
|
||||
color: #12233b;
|
||||
background: linear-gradient(#fff, #c8c8c8);
|
||||
border: 2px outset #fff;
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 0;
|
||||
}
|
||||
.lb-x:active { border-style: inset; }
|
||||
|
||||
@keyframes lb-spin { to { transform: rotate(360deg); } }
|
||||
@keyframes lb-slide { from { transform: translateX(0); } to { transform: translateX(-50%); } }
|
||||
@keyframes lb-blink { 50% { opacity: 0.4; } }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.lb-track, .lb-burst, .lb-btn { animation: none !important; }
|
||||
body, nav.fixed, .lb-root { transition: none !important; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const KEY = "snapotter-v2-banner";
|
||||
const root = document.querySelector("[data-lb]");
|
||||
if (!root) return;
|
||||
const close = root.querySelector("[data-lb-close]");
|
||||
close?.addEventListener("click", () => {
|
||||
try {
|
||||
localStorage.setItem(KEY, "dismissed");
|
||||
} catch (e) {
|
||||
/* private mode / storage disabled: just hide for this session */
|
||||
}
|
||||
document.documentElement.setAttribute("data-lb-dismissed", "");
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -2,6 +2,7 @@
|
||||
// biome-ignore-all lint/correctness/noUnusedVariables: Astro template consumes frontmatter values.
|
||||
import "@/styles/globals.css";
|
||||
import { TOOLS } from "@snapotter/shared";
|
||||
import LaunchBanner from "@/components/LaunchBanner.astro";
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
@@ -32,6 +33,16 @@ const {
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<!-- TEMP: 2.0 launch banner dismissal. Render-blocking so a returning visitor
|
||||
who closed the banner never sees it flash. Remove with the banner. -->
|
||||
<script is:inline>
|
||||
try {
|
||||
if (localStorage.getItem("snapotter-v2-banner") === "dismissed") {
|
||||
document.documentElement.setAttribute("data-lb-dismissed", "");
|
||||
}
|
||||
} catch (e) {}
|
||||
</script>
|
||||
|
||||
<title>{title}</title>
|
||||
<meta name="description" content={description} />
|
||||
<link rel="canonical" href={canonical} />
|
||||
@@ -84,6 +95,7 @@ const {
|
||||
</head>
|
||||
<body>
|
||||
<a href="#main" class="skip-link">Skip to content</a>
|
||||
<LaunchBanner />
|
||||
<slot />
|
||||
|
||||
<!-- Scroll-triggered reveal observer -->
|
||||
|
||||
Reference in New Issue
Block a user