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

View File

@ -8,6 +8,10 @@ type ScrollFilmProps = {
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) {
const orbitRef = useRef<HTMLDivElement>(null);
const filmRef = useRef<HTMLVideoElement>(null);
@ -30,6 +34,9 @@ export function ScrollFilm({ src, poster, children }: ScrollFilmProps) {
let current = 0;
let frame = 0;
let blobUrl: string | null = null;
let introStartedAt = 0;
let introDone = false;
const introMs = 2400;
const markReady = () => {
if (ready) {
@ -39,6 +46,9 @@ export function ScrollFilm({ src, poster, children }: ScrollFilmProps) {
film.pause();
film.classList.add("is-ready");
setLoading(false);
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
introDone = reduce;
introStartedAt = reduce ? 0 : performance.now();
};
fetch(src)
@ -59,13 +69,33 @@ export function ScrollFilm({ src, poster, children }: ScrollFilmProps) {
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();
current += (target - current) * 0.42;
orbit.style.setProperty("--scrub", current.toFixed(4));
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) {
film.currentTime = time;
}
@ -104,9 +134,8 @@ export function ScrollFilm({ src, poster, children }: ScrollFilmProps) {
</div>
<div className="orbit-story">{children}</div>
{loading ? (
<div className="orbit-loader" role="status" aria-live="polite">
<span className="orbit-loader-dot" />
<p>Carregando Sinka</p>
<div className="orbit-loader" role="status" aria-live="polite" aria-label="Sinka">
<img src="/brand/sinka-logo.webp" alt="Sinka" className="orbit-loader-logo" />
</div>
) : null}
</div>

View File

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