entrada suave no video, loader com logo e login vazio

This commit is contained in:
Armando Soares 2026-09-16 05:59:22 +00:00
parent d0f3ba8655
commit 6cc1a2bc00
3 changed files with 42 additions and 25 deletions

View File

@ -88,34 +88,24 @@ h3,
z-index: 50; z-index: 50;
inset: 0; inset: 0;
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 1rem;
background: #000; background: #000;
color: #e8f4e5;
letter-spacing: 0.18em;
text-transform: uppercase;
font-size: 0.72rem;
} }
.orbit-loader-dot { .orbit-loader-logo {
width: 10px; width: min(11.5rem, 58vw);
height: 10px; height: auto;
border-radius: 999px; animation: loader-glow 1.6s ease-in-out infinite;
background: #82c85c;
animation: loader-pulse 1s ease-in-out infinite;
} }
@keyframes loader-pulse { @keyframes loader-glow {
0%, 0%,
100% { 100% {
opacity: 0.35; opacity: 0.4;
transform: scale(1);
} }
50% { 50% {
opacity: 1; opacity: 1;
transform: scale(1.35);
} }
} }

View File

@ -8,6 +8,10 @@ type ScrollFilmProps = {
children: ReactNode; children: ReactNode;
}; };
function easeInOutCubic(value: number) {
return value < 0.5 ? 4 * value * value * value : 1 - (-2 * value + 2) ** 3 / 2;
}
export function ScrollFilm({ src, poster, children }: ScrollFilmProps) { export function ScrollFilm({ src, poster, children }: ScrollFilmProps) {
const orbitRef = useRef<HTMLDivElement>(null); const orbitRef = useRef<HTMLDivElement>(null);
const filmRef = useRef<HTMLVideoElement>(null); const filmRef = useRef<HTMLVideoElement>(null);
@ -30,6 +34,9 @@ export function ScrollFilm({ src, poster, children }: ScrollFilmProps) {
let current = 0; let current = 0;
let frame = 0; let frame = 0;
let blobUrl: string | null = null; let blobUrl: string | null = null;
let introStartedAt = 0;
let introDone = false;
const introMs = 2400;
const markReady = () => { const markReady = () => {
if (ready) { if (ready) {
@ -39,6 +46,9 @@ export function ScrollFilm({ src, poster, children }: ScrollFilmProps) {
film.pause(); film.pause();
film.classList.add("is-ready"); film.classList.add("is-ready");
setLoading(false); setLoading(false);
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
introDone = reduce;
introStartedAt = reduce ? 0 : performance.now();
}; };
fetch(src) fetch(src)
@ -59,13 +69,33 @@ export function ScrollFilm({ src, poster, children }: ScrollFilmProps) {
return y / total; return y / total;
}; };
const tick = () => { const introSeconds = (now: number, duration: number) => {
if (introDone || !introStartedAt) {
return null;
}
if (progressFromScroll() > 0.02) {
introDone = true;
return null;
}
const elapsed = now - introStartedAt;
const t = Math.min(elapsed / introMs, 1);
const peak = Math.min(2, Math.max(duration * 0.22, 0.6));
const trip = t < 0.5 ? easeInOutCubic(t * 2) : 1 - easeInOutCubic((t - 0.5) * 2);
if (t >= 1) {
introDone = true;
return 0;
}
return trip * peak;
};
const tick = (now: number) => {
const target = progressFromScroll(); const target = progressFromScroll();
current += (target - current) * 0.42; current += (target - current) * 0.42;
orbit.style.setProperty("--scrub", current.toFixed(4)); orbit.style.setProperty("--scrub", current.toFixed(4));
if (ready && film.duration && !film.seeking) { if (ready && film.duration && !film.seeking) {
const time = current * (film.duration - 0.04); const intro = introSeconds(now, film.duration);
const time = intro ?? current * (film.duration - 0.04);
if (Math.abs(film.currentTime - time) >= 0.02) { if (Math.abs(film.currentTime - time) >= 0.02) {
film.currentTime = time; film.currentTime = time;
} }
@ -104,9 +134,8 @@ export function ScrollFilm({ src, poster, children }: ScrollFilmProps) {
</div> </div>
<div className="orbit-story">{children}</div> <div className="orbit-story">{children}</div>
{loading ? ( {loading ? (
<div className="orbit-loader" role="status" aria-live="polite"> <div className="orbit-loader" role="status" aria-live="polite" aria-label="Sinka">
<span className="orbit-loader-dot" /> <img src="/brand/sinka-logo.webp" alt="Sinka" className="orbit-loader-logo" />
<p>Carregando Sinka</p>
</div> </div>
) : null} ) : null}
</div> </div>

View File

@ -19,10 +19,8 @@ export function LoginForm({
google_unlinked: "Este e-mail Google não tem acesso à administração.", google_unlinked: "Este e-mail Google não tem acesso à administração.",
google_denied: "Não foi possível entrar com o Google.", google_denied: "Não foi possível entrar com o Google.",
}[search.get("error") ?? ""]; }[search.get("error") ?? ""];
const [email, setEmail] = useState( const [email, setEmail] = useState("");
variant === "admin" ? "tina.r@example.net" : "xena.w@example.org", const [password, setPassword] = useState("");
);
const [password, setPassword] = useState(variant === "admin" ? "SinkaPlatform!1" : "SinkaAdmin!1");
const [error, setError] = useState(""); const [error, setError] = useState("");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);