Fix temp route typecheck

master
dev 5 months ago
parent 2f4521dd74
commit 16d4578aa1

@ -3,6 +3,7 @@
Estado al cierre:
- Suite unitaria verde: `npm test` -> 103 archivos, 1189 tests.
- Typecheck verde: `npm run check` -> 0 errores, 0 warnings.
- `fmts` verde: `npx vitest run src/arts/fmts` -> 14 archivos, 39 tests.
- `conn` verde: `npx vitest run src/arts/conn` -> 5 archivos, 28 tests.
- `auth` verde: `npx vitest run src/arts/auth src/svrs/auth src/libs/auth` -> 4 archivos, 14 tests.
@ -62,7 +63,7 @@ Estado al cierre:
- `src/arts/conn/README.md` ampliado: contrato de raíz/conexión/canal, estados, transportes, request/reply, reconnect, heartbeat, sesión, diagnostics/logger, errores y testing.
- `src/arts/fmts/README.md` ampliado con guia de uso, `LocaleSource`, contrato auto/manual, submodulos, listeners, integracion con `aapp` y tests.
- `fmts` redujo boilerplate activo con helpers `readFrom` / `writeTo` y los engines comparten directamente las funciones de `createFormatsLocaleState`; APIs públicas sin cambios.
- No tocar `src/web/routes/temp/` hasta decidir que hacer con esa pagina.
- `src/web/routes/temp/` corregido: scripts tipados, warnings Svelte eliminados y compatible con `npm run check`.
- No commitear `.idea/`, `.claude/` ni `.opencode/`.
Pendiente para manana:
@ -71,4 +72,4 @@ Pendiente para manana:
- Continuar la reduccion de archivos grandes: prioridad `conn/connection.ts`, extraccion progresiva de runtime read/write en `libs/cach` y separar helpers de test/server auth cuando crezcan.
- Ampliar tests de integracion cruzada: `auth + sess + perm + cach + http + stor + fmts + conn + timr + logr`.
- Revisar la adopcion final del contrato comun `Logger` / diagnostics en todos los modulos, sin acoplar artefactos a `arts/logr`; primera pasada limpia salvo `aapp` como composition root, `arts/logr` y tests.
- Decidir que hacer con la pagina temporal que bloquea `npm run check`; mientras tanto, validar con `npm test` y tests focalizados.
- Mantener `npm run check` dentro de la validacion regular junto a `npm test`.

@ -30,7 +30,7 @@ function context(schemaVersion = 'Item:v1'): ResolvedCacheContext {
};
}
function envelope(value: unknown, schemaVersion = 'Item:v1'): CacheEnvelope<unknown> {
function envelope<T>(value: T, schemaVersion = 'Item:v1'): CacheEnvelope<T> {
return {
value,
meta: {
@ -103,10 +103,13 @@ describe('runCacheMutation', () => {
contextFor: vi.fn(async () => context()),
getEnvelope: vi.fn(async () => envelope(1)),
safeDelete: vi.fn(),
createEnvelope: vi.fn(async (value, _ctx, tags) => ({
...envelope(value),
meta: { ...envelope(value).meta, tags }
})),
createEnvelope: async <T>(value: T, _ctx: ResolvedCacheContext, tags: string[]) => {
const next = envelope(value);
return {
...next,
meta: { ...next.meta, tags }
};
},
writeEnvelope: vi.fn(async (_ctx, next) => {
written.push(next);
})

@ -67,7 +67,7 @@ describe('refreshCacheInBackground', () => {
{ key: ['item'], scope: CACHE_SCOPE_PUBLIC, fetcher: async () => 'next' },
{
singleflight: { has: () => false, run: vi.fn(async (_key, fn) => fn()) },
fetchAndStore: vi.fn(async () => 'next'),
fetchAndStore: async <T>() => 'next' as T,
emit: (type) => {
events.push(type);
}

@ -0,0 +1,302 @@
<script lang="ts">
const images = [
"https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?w=1200",
"https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=1200",
"https://images.unsplash.com/photo-1493246507139-91e8fad9978e?w=1200",
"https://images.unsplash.com/photo-1518837695005-2083093ee35b?w=1200",
"https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=1200",
"https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=1200"
];
let section: HTMLElement | undefined;
let progress = $state(0);
const clamp = (value: number, min = 0, max = 1) => {
return Math.min(max, Math.max(min, value));
};
const eased = $derived(0.5 - Math.cos(progress * Math.PI) / 2);
const angle = $derived(72 - eased * 72);
const bladeWidth = $derived(70 + eased * 170);
const cosA = $derived(Math.cos((angle * Math.PI) / 180));
const overlap = $derived(2 * bladeWidth * (1 - cosA));
const scale = $derived(0.92 + eased * 0.08);
$effect(() => {
if (!section) return;
const scrollSection = section;
let frame: number | null = null;
const update = () => {
const rect = scrollSection.getBoundingClientRect();
const scrollable = scrollSection.offsetHeight - window.innerHeight;
if (scrollable <= 0) {
progress = 0;
return;
}
progress = clamp(-rect.top / scrollable);
frame = null;
};
const onScroll = () => {
if (frame === null) {
frame = requestAnimationFrame(update);
}
};
update();
window.addEventListener("scroll", onScroll, { passive: true });
window.addEventListener("resize", update);
return () => {
window.removeEventListener("scroll", onScroll);
window.removeEventListener("resize", update);
if (frame !== null) {
cancelAnimationFrame(frame);
}
};
});
</script>
<section class="scroll-section" bind:this={section}>
<div class="sticky-stage">
<div class="intro">
<span>Cortina de fuelle</span>
<h1>Scroll para abrir</h1>
<p>
Cada imagen ocupa una lama vertical. El scroll controla la apertura,
la separación y el ángulo de giro.
</p>
</div>
<div
class="accordion"
style="
--angle: {angle}deg;
--blade-width: {bladeWidth}px;
--overlap: {overlap}px;
--scale: {scale};
"
>
{#each images as image, index}
<article
class:left={index % 2 === 0}
class:right={index % 2 !== 0}
class="blade"
style="
--image: url('{image}');
--z: {images.length - index};
"
>
<div class="blade-face">
<div class="shine"></div>
</div>
</article>
{/each}
</div>
<div class="progress-bar">
<div style="width: {progress * 100}%"></div>
</div>
</div>
</section>
<style>
:global(body) {
margin: 0;
background: #070707;
color: white;
font-family:
Inter,
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
}
.scroll-section {
height: 320vh;
position: relative;
background:
radial-gradient(circle at top, rgba(255, 255, 255, 0.14), transparent 36rem),
linear-gradient(180deg, #111, #050505);
}
.sticky-stage {
height: 100vh;
position: sticky;
top: 0;
display: grid;
place-items: center;
overflow: hidden;
perspective: 1400px;
}
.intro {
position: absolute;
top: 7vh;
left: clamp(1.5rem, 5vw, 5rem);
z-index: 10;
max-width: 520px;
pointer-events: none;
}
.intro span {
display: inline-block;
margin-bottom: 0.75rem;
opacity: 0.65;
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.18em;
}
.intro h1 {
margin: 0;
font-size: clamp(2.5rem, 6vw, 6rem);
line-height: 0.95;
letter-spacing: -0.06em;
}
.intro p {
max-width: 34rem;
margin-top: 1.2rem;
color: rgba(255, 255, 255, 0.68);
font-size: clamp(1rem, 1.5vw, 1.2rem);
line-height: 1.6;
}
.accordion {
display: flex;
align-items: center;
justify-content: center;
width: min(94vw, 1320px);
min-height: 540px;
transform-style: preserve-3d;
transform: scale(var(--scale)) rotateX(2deg);
}
.blade {
width: var(--blade-width);
height: min(64vh, 560px);
flex: 0 0 var(--blade-width);
position: relative;
z-index: var(--z);
transform-style: preserve-3d;
transition:
width 80ms linear,
flex-basis 80ms linear,
margin-left 80ms linear,
transform 80ms linear;
}
.blade.left {
transform-origin: left center;
transform: rotateY(var(--angle));
}
.blade.right {
margin-left: calc(var(--overlap) * -1);
transform-origin: right center;
transform: rotateY(calc(var(--angle) * -1));
}
.blade-face {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
background-image: var(--image);
background-size: cover;
background-position: center;
backface-visibility: hidden;
box-shadow: 0 32px 80px rgba(0, 0, 0, 0.55);
}
.blade:first-child .blade-face {
border-top-left-radius: 24px;
border-bottom-left-radius: 24px;
box-shadow:
0 32px 80px rgba(0, 0, 0, 0.55),
inset 1px 0 0 rgba(255, 255, 255, 0.16);
}
.blade:last-child .blade-face {
border-top-right-radius: 24px;
border-bottom-right-radius: 24px;
box-shadow:
0 32px 80px rgba(0, 0, 0, 0.55),
inset -1px 0 0 rgba(255, 255, 255, 0.16);
}
.blade-face::after {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(
90deg,
rgba(0, 0, 0, 0.46),
transparent 35%,
rgba(255, 255, 255, 0.18) 55%,
rgba(0, 0, 0, 0.36)
);
mix-blend-mode: soft-light;
pointer-events: none;
}
.shine {
position: absolute;
inset: 0;
background:
linear-gradient(
115deg,
transparent 0%,
rgba(255, 255, 255, 0.22) 45%,
transparent 62%
);
opacity: 0.35;
transform: translateX(-35%);
pointer-events: none;
}
.progress-bar {
position: absolute;
bottom: 6vh;
left: 50%;
width: min(420px, 70vw);
height: 3px;
overflow: hidden;
border-radius: 999px;
transform: translateX(-50%);
background: rgba(255, 255, 255, 0.16);
}
.progress-bar div {
height: 100%;
border-radius: inherit;
background: white;
}
@media (max-width: 760px) {
.accordion {
width: 125vw;
min-height: 430px;
}
.blade {
height: 52vh;
}
.intro {
top: 5vh;
}
}
</style>

@ -0,0 +1,92 @@
<script lang="ts">
import CurtainScroll from "./Curtain.svelte";
</script>
<CurtainScroll
image="https://images.unsplash.com/photo-1483985988355-763728e1935b?w=2400&q=80"
bladeCount={8}
maxAngle={82}
smoothing={0.12}
anchor="right"
scrollLength="240vh"
>
<div class="cta">
<div class="cta-content">
<span class="cta-eyebrow">Solo este fin de semana</span>
<h2>Hasta -40% en toda la colección</h2>
<p>
Las piezas más buscadas vuelven con descuento. Cuando la cortina cae, no
vuelve a abrirse.
</p>
<a href="/" class="cta-button">Comprar ahora</a>
</div>
</div>
</CurtainScroll>
<style>
.cta {
width: 100%;
height: 100%;
position: absolute;
inset: 0;
display: grid;
place-items: center;
text-align: center;
background:
radial-gradient(circle at 25% 30%, rgba(255, 90, 40, 0.28), transparent 55%),
radial-gradient(circle at 75% 70%, rgba(120, 70, 230, 0.22), transparent 55%),
#0a0a0c;
color: white;
z-index: 1;
}
.cta-content {
max-width: 760px;
padding: 0 2rem;
text-align: center;
}
.cta-eyebrow {
display: inline-block;
margin-bottom: 1.5rem;
padding: 0.42rem 1rem;
border: 1px solid rgba(255, 255, 255, 0.22);
border-radius: 999px;
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.18em;
}
.cta-content h2 {
margin: 0;
font-size: clamp(2.5rem, 7vw, 5.5rem);
line-height: 1;
letter-spacing: -0.045em;
}
.cta-content p {
max-width: 38rem;
margin: 1.4rem auto 2.4rem;
color: rgba(255, 255, 255, 0.72);
font-size: clamp(1rem, 1.4vw, 1.18rem);
line-height: 1.55;
}
.cta-button {
display: inline-flex;
align-items: center;
gap: 0.65rem;
padding: 1rem 2rem;
background: white;
color: #0a0a0c;
border-radius: 999px;
font-weight: 600;
font-size: 1rem;
text-decoration: none;
transition: transform 0.2s ease;
}
.cta-button:hover {
transform: translateY(-2px);
}
</style>

@ -0,0 +1,294 @@
<script lang="ts">
import type { Snippet } from "svelte";
type CurtainAnchor = "left" | "right";
interface CurtainProps {
image: string;
bladeCount?: number;
maxAngle?: number;
scrollLength?: string;
smoothing?: number;
anchor?: CurtainAnchor;
radius?: string;
height?: string;
width?: string;
children?: Snippet;
}
let {
image,
bladeCount = 8,
maxAngle = 82,
scrollLength = "240vh",
smoothing = 0.12,
anchor = "right",
radius = "24px",
height = "min(80vh, 720px)",
width = "min(96vw, 1280px)",
children
}: CurtainProps = $props();
const blades = $derived(
Array.from({ length: bladeCount }, (_, i) => i)
);
let section: HTMLElement | undefined;
let progress = $state(0);
const clamp = (value: number, min = 0, max = 1) => Math.min(max, Math.max(min, value));
const eased = $derived(0.5 - Math.cos(progress * Math.PI) / 2);
const angle = $derived(eased * maxAngle);
const cosA = $derived(Math.cos((angle * Math.PI) / 180));
const overlapFactor = $derived(2 * (1 - cosA));
const fold = $derived(eased);
const spread = $derived(1 - eased * eased * 0.78);
$effect(() => {
if (!section) return;
const scrollSection = section;
let target = 0;
let rafId: number | null = null;
const reduced =
typeof window !== "undefined" &&
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const lerpFactor = reduced ? 1 : smoothing;
const tick = () => {
const diff = target - progress;
if (Math.abs(diff) < 0.0005) {
progress = target;
rafId = null;
return;
}
progress += diff * lerpFactor;
rafId = requestAnimationFrame(tick);
};
const updateTarget = () => {
const rect = scrollSection.getBoundingClientRect();
const scrollable = scrollSection.offsetHeight - window.innerHeight;
target = scrollable <= 0 ? 0 : clamp(-rect.top / scrollable);
if (rafId === null) {
rafId = requestAnimationFrame(tick);
}
};
updateTarget();
window.addEventListener("scroll", updateTarget, { passive: true });
window.addEventListener("resize", updateTarget);
return () => {
window.removeEventListener("scroll", updateTarget);
window.removeEventListener("resize", updateTarget);
if (rafId !== null) cancelAnimationFrame(rafId);
};
});
</script>
<section
class="curtain-section"
bind:this={section}
style="--scroll-length: {scrollLength};"
>
<div class="sticky-stage">
<div class="background-layer">
{@render children?.()}
</div>
<div
class="ground-shadow"
class:anchor-right={anchor === "right"}
class:anchor-left={anchor === "left"}
style="
--fold: {fold};
--spread: {spread};
--curtain-w: {width};
--curtain-h: {height};
"
></div>
<div
class="curtain"
class:anchor-right={anchor === "right"}
class:anchor-left={anchor === "left"}
style="
--angle: {angle}deg;
--overlap-factor: {overlapFactor};
--fold: {fold};
--n: {bladeCount};
--radius: {radius};
--curtain-w: {width};
--curtain-h: {height};
"
>
{#each blades as i (i)}
<article
class:left={i % 2 === 0}
class:right={i % 2 !== 0}
class="blade"
style="
--image: url('{image}');
--bg-pos: {(i / Math.max(1, bladeCount - 1)) * 100}%;
--z: {bladeCount - i};
"
>
<div class="blade-face"></div>
</article>
{/each}
</div>
</div>
</section>
<style>
.curtain-section {
height: var(--scroll-length);
position: relative;
}
.sticky-stage {
height: 100vh;
position: sticky;
top: 0;
display: grid;
place-items: center;
overflow: hidden;
perspective: 1100px;
perspective-origin: 50% 35%;
}
.background-layer {
position: absolute;
inset: 0;
z-index: 1;
}
.ground-shadow {
position: absolute;
top: 50%;
left: 50%;
width: calc(var(--curtain-w) * var(--spread));
height: calc(var(--curtain-h) * 0.18);
transform: translate(-50%, calc(var(--curtain-h) * 0.5 - 10%));
z-index: 4;
pointer-events: none;
background: radial-gradient(
ellipse at center,
rgba(0, 0, 0, calc(0.7 + 0.2 * var(--fold))) 0%,
rgba(0, 0, 0, 0.4) 35%,
rgba(0, 0, 0, 0.15) 60%,
transparent 80%
);
filter: blur(24px);
}
.ground-shadow.anchor-right {
left: auto;
right: 0;
transform: translate(0, calc(var(--curtain-h) * 0.5 - 10%));
}
.ground-shadow.anchor-left {
left: 0;
transform: translate(0, calc(var(--curtain-h) * 0.5 - 10%));
}
.curtain {
position: relative;
z-index: 5;
width: var(--curtain-w);
height: var(--curtain-h);
display: flex;
align-items: stretch;
transform-style: preserve-3d;
transform: rotateX(2deg);
pointer-events: none;
}
.curtain.anchor-right {
justify-content: flex-end;
}
.curtain.anchor-left {
justify-content: flex-start;
}
.blade {
flex: 0 0 calc(100% / var(--n));
width: calc(100% / var(--n));
height: 100%;
position: relative;
z-index: var(--z);
transform-style: preserve-3d;
will-change: transform;
backface-visibility: hidden;
}
.blade.left {
transform-origin: left center;
transform: rotateY(var(--angle));
}
.blade.right {
margin-left: calc(100% / var(--n) * var(--overlap-factor) * -1);
transform-origin: right center;
transform: rotateY(calc(var(--angle) * -1));
}
.blade-face {
position: absolute;
inset: 0;
background-image: var(--image);
background-size: calc(var(--n) * 100%) 100%;
background-position: var(--bg-pos) 0;
background-repeat: no-repeat;
backface-visibility: hidden;
transform: scaleX(1.006);
}
.blade-face::after {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
}
.blade.left .blade-face::after {
background: linear-gradient(
90deg,
rgba(255, 255, 255, calc(0.08 * var(--fold))) 0%,
transparent 35%,
transparent 60%,
rgba(0, 0, 0, calc(0.65 * var(--fold))) 100%
);
}
.blade.right .blade-face::after {
background: linear-gradient(
90deg,
rgba(0, 0, 0, calc(0.65 * var(--fold))) 0%,
transparent 40%,
transparent 65%,
rgba(255, 255, 255, calc(0.08 * var(--fold))) 100%
);
}
.blade:first-child .blade-face {
border-top-left-radius: var(--radius);
border-bottom-left-radius: var(--radius);
}
.blade:last-child .blade-face {
border-top-right-radius: var(--radius);
border-bottom-right-radius: var(--radius);
}
</style>
Loading…
Cancel
Save

Powered by TurnKey Linux.