Hendidura del lomo en las páginas y en la hoja que gira
En doble página va en el centro, en una página en el lado del lomo según el sentido de lectura y en una página ancha en su centro. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+37
-8
@@ -1481,6 +1481,12 @@ async function viewReader(mangaId, chapterId, startPage) {
|
|||||||
scheduleLeafTextures()
|
scheduleLeafTextures()
|
||||||
}
|
}
|
||||||
const imgReady = im => im.complete && im.naturalWidth > 0
|
const imgReady = im => im.complete && im.naturalWidth > 0
|
||||||
|
// Lado de la hendidura del lomo: l o r (borde de la página) o m (página doble, en el centro).
|
||||||
|
const gutterOf = (i, n) => {
|
||||||
|
if (wide.has(i)) return 'm'
|
||||||
|
const rtl = settings.dir === 'rtl'
|
||||||
|
return shown.length === 2 ? (rtl === (n === 0) ? 'l' : 'r') : rtl ? 'r' : 'l'
|
||||||
|
}
|
||||||
// Hacia una página que aún no ha llegado, la actual se queda (con un indicador tras un momento) hasta que
|
// Hacia una página que aún no ha llegado, la actual se queda (con un indicador tras un momento) hasta que
|
||||||
// llegue o pasen 4 s, en vez de pasar la hoja y dejar negro debajo. Si se pide otra página mientras,
|
// llegue o pasen 4 s, en vez de pasar la hoja y dejar negro debajo. Si se pide otra página mientras,
|
||||||
// manda la última.
|
// manda la última.
|
||||||
@@ -1494,7 +1500,7 @@ async function viewReader(mangaId, chapterId, startPage) {
|
|||||||
const stage = document.createElement('div')
|
const stage = document.createElement('div')
|
||||||
stage.className = 'stage'
|
stage.className = 'stage'
|
||||||
stage.classList.toggle('two', shown.length === 2)
|
stage.classList.toggle('two', shown.length === 2)
|
||||||
stage.innerHTML = shown.map(i => `<img data-i="${i}" src="${pages[i]}" alt="" draggable="false">`).join('')
|
stage.innerHTML = shown.map((i, n) => `<img data-i="${i}" data-gutter="${gutterOf(i, n)}" src="${pages[i]}" alt="" draggable="false">`).join('')
|
||||||
const imgs = [...stage.querySelectorAll('img')]
|
const imgs = [...stage.querySelectorAll('img')]
|
||||||
if ($('.stage', box) && dir && !wantDrag && !imgs.every(imgReady)) {
|
if ($('.stage', box) && dir && !wantDrag && !imgs.every(imgReady)) {
|
||||||
waitTimer = setTimeout(() => box.classList.add('waiting'), 200)
|
waitTimer = setTimeout(() => box.classList.add('waiting'), 200)
|
||||||
@@ -1534,6 +1540,7 @@ async function viewReader(mangaId, chapterId, startPage) {
|
|||||||
else { box.replaceChildren(stage) }
|
else { box.replaceChildren(stage) }
|
||||||
stage.querySelectorAll('img').forEach(img => img.addEventListener('load', () => {
|
stage.querySelectorAll('img').forEach(img => img.addEventListener('load', () => {
|
||||||
const i = Number(img.dataset.i)
|
const i = Number(img.dataset.i)
|
||||||
|
if (isWide(img)) img.dataset.gutter = 'm'
|
||||||
if (isWide(img) && !wide.has(i)) {
|
if (isWide(img) && !wide.has(i)) {
|
||||||
wide.add(i)
|
wide.add(i)
|
||||||
if (shown.length === 2 && shown.includes(i)) goTo(shown[0], false, true) // reagrupar
|
if (shown.length === 2 && shown.includes(i)) goTo(shown[0], false, true) // reagrupar
|
||||||
@@ -1645,19 +1652,28 @@ async function viewReader(mangaId, chapterId, startPage) {
|
|||||||
gl.attachShader(prog, shader(gl.FRAGMENT_SHADER, `#version 300 es
|
gl.attachShader(prog, shader(gl.FRAGMENT_SHADER, `#version 300 es
|
||||||
precision highp float;
|
precision highp float;
|
||||||
uniform sampler2D uFront, uBack;
|
uniform sampler2D uFront, uBack;
|
||||||
uniform bool uPaper;
|
uniform bool uPaper, uSpineLeft, uSpread;
|
||||||
in vec2 vUV;
|
in vec2 vUV;
|
||||||
in float vC;
|
in float vC;
|
||||||
out vec4 color;
|
out vec4 color;
|
||||||
|
// La hendidura del lomo: los mismos puntos que data-gutter en style.css, para que no salte al empezar el giro.
|
||||||
|
float gutter(float d) {
|
||||||
|
if (d < 0.012) return mix(0.4, 0.6, d / 0.012);
|
||||||
|
if (d < 0.03) return mix(0.6, 0.8, (d - 0.012) / 0.018);
|
||||||
|
if (d < 0.06) return mix(0.8, 0.93, (d - 0.03) / 0.03);
|
||||||
|
if (d < 0.1) return mix(0.93, 1.0, (d - 0.06) / 0.04);
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
void main() {
|
void main() {
|
||||||
vec4 t;
|
vec4 t;
|
||||||
float b;
|
float b;
|
||||||
|
float fromSpine = uSpineLeft ? vUV.x : 1.0 - vUV.x;
|
||||||
if (gl_FrontFacing) {
|
if (gl_FrontFacing) {
|
||||||
t = texture(uFront, vUV, -0.5);
|
t = texture(uFront, vUV, -0.5);
|
||||||
b = 1.0 - (1.0 - vC) * 0.3;
|
b = (1.0 - (1.0 - vC) * 0.3) * gutter(uSpread ? abs(vUV.x - 0.5) * 2.0 : fromSpine);
|
||||||
} else {
|
} else {
|
||||||
t = uPaper ? vec4(0.914, 0.898, 0.863, 1.0) : texture(uBack, vec2(1.0 - vUV.x, vUV.y), -0.5);
|
t = uPaper ? vec4(0.914, 0.898, 0.863, 1.0) : texture(uBack, vec2(1.0 - vUV.x, vUV.y), -0.5);
|
||||||
b = 1.0 - (1.0 + vC) * 0.3;
|
b = (1.0 - (1.0 + vC) * 0.3) * gutter(fromSpine);
|
||||||
}
|
}
|
||||||
color = vec4(t.rgb * b, t.a);
|
color = vec4(t.rgb * b, t.a);
|
||||||
}`))
|
}`))
|
||||||
@@ -1743,7 +1759,7 @@ async function viewReader(mangaId, chapterId, startPage) {
|
|||||||
canvas.addEventListener('webglcontextlost', () => { if (leafGL?.gl === gl) { leafGL = null; leafTextures.clear() } })
|
canvas.addEventListener('webglcontextlost', () => { if (leafGL?.gl === gl) { leafGL = null; leafTextures.clear() } })
|
||||||
leafGL = { canvas, gl, prog, posBuf, uvBuf, aniso, shadowProg, shadowVao, shadowBuf, shadowCount: m, shadowData: new Float32Array(shadowSegs * 16 * 5),
|
leafGL = { canvas, gl, prog, posBuf, uvBuf, aniso, shadowProg, shadowVao, shadowBuf, shadowCount: m, shadowData: new Float32Array(shadowSegs * 16 * 5),
|
||||||
uSize: gl.getUniformLocation(shadowProg, 'uSize'), positions: new Float32Array(count * 4), uvs: new Float32Array(count * 2), indexCount: n,
|
uSize: gl.getUniformLocation(shadowProg, 'uSize'), positions: new Float32Array(count * 4), uvs: new Float32Array(count * 2), indexCount: n,
|
||||||
uView: gl.getUniformLocation(prog, 'uView'), uPaper: gl.getUniformLocation(prog, 'uPaper'), maxSize: gl.getParameter(gl.MAX_TEXTURE_SIZE) }
|
uView: gl.getUniformLocation(prog, 'uView'), uPaper: gl.getUniformLocation(prog, 'uPaper'), uSpineLeft: gl.getUniformLocation(prog, 'uSpineLeft'), uSpread: gl.getUniformLocation(prog, 'uSpread'), maxSize: gl.getParameter(gl.MAX_TEXTURE_SIZE) }
|
||||||
return leafGL
|
return leafGL
|
||||||
}
|
}
|
||||||
function leafTexture(im) {
|
function leafTexture(im) {
|
||||||
@@ -1817,6 +1833,8 @@ async function viewReader(mangaId, chapterId, startPage) {
|
|||||||
const scale = pw / W
|
const scale = pw / W
|
||||||
gl.scissor(Math.round(paper.l * scale), Math.round((H - paper.t - paper.h) * scale), Math.round(paper.w * scale), Math.round(paper.h * scale))
|
gl.scissor(Math.round(paper.l * scale), Math.round((H - paper.t - paper.h) * scale), Math.round(paper.w * scale), Math.round(paper.h * scale))
|
||||||
gl.uniform1i(L.uPaper, back ? 0 : 1)
|
gl.uniform1i(L.uPaper, back ? 0 : 1)
|
||||||
|
gl.uniform1i(L.uSpineLeft, spineLeft ? 1 : 0)
|
||||||
|
gl.uniform1i(L.uSpread, frontIm.dataset.gutter === 'm' ? 1 : 0)
|
||||||
gl.activeTexture(gl.TEXTURE0)
|
gl.activeTexture(gl.TEXTURE0)
|
||||||
gl.bindTexture(gl.TEXTURE_2D, front)
|
gl.bindTexture(gl.TEXTURE_2D, front)
|
||||||
gl.activeTexture(gl.TEXTURE1)
|
gl.activeTexture(gl.TEXTURE1)
|
||||||
@@ -2033,10 +2051,15 @@ async function viewReader(mangaId, chapterId, startPage) {
|
|||||||
spineLeft = sign > 0
|
spineLeft = sign > 0
|
||||||
const keep = document.createElement('img')
|
const keep = document.createElement('img')
|
||||||
keep.className = 'leafkeep'
|
keep.className = 'leafkeep'
|
||||||
|
keep.dataset.gutter = stay.dataset.gutter
|
||||||
keep.src = stay.src
|
keep.src = stay.src
|
||||||
place(keep, rel(stay))
|
place(keep, rel(stay))
|
||||||
box.append(keep)
|
// Su hendidura es transparente: sin negro detrás, se vería la página nueva y la hendidura se aclararía.
|
||||||
extra.push(keep)
|
const keepBack = document.createElement('i')
|
||||||
|
keepBack.className = 'leafkeepback'
|
||||||
|
place(keepBack, rel(stay))
|
||||||
|
box.append(keepBack, keep)
|
||||||
|
extra.push(keepBack, keep)
|
||||||
old.style.visibility = 'hidden' // la hoja y la copia ocupan su lugar
|
old.style.visibility = 'hidden' // la hoja y la copia ocupan su lugar
|
||||||
} else {
|
} else {
|
||||||
// Una página: el lomo está a la izquierda en izq→der y a la derecha en der→izq. Hacia delante, la
|
// Una página: el lomo está a la izquierda en izq→der y a la derecha en der→izq. Hacia delante, la
|
||||||
@@ -2375,7 +2398,13 @@ async function viewReader(mangaId, chapterId, startPage) {
|
|||||||
render()
|
render()
|
||||||
})
|
})
|
||||||
$('#fit').addEventListener('change', e => { settings.fit = e.target.value; savePrefs(); render() })
|
$('#fit').addEventListener('change', e => { settings.fit = e.target.value; savePrefs(); render() })
|
||||||
$('#dir').addEventListener('change', e => { settings.dir = e.target.value; box.dataset.dir = settings.dir; syncDirUi(); savePrefs() })
|
$('#dir').addEventListener('change', e => {
|
||||||
|
settings.dir = e.target.value
|
||||||
|
box.dataset.dir = settings.dir
|
||||||
|
box.querySelectorAll('.stage img').forEach((im, n) => { im.dataset.gutter = gutterOf(Number(im.dataset.i), n) })
|
||||||
|
syncDirUi()
|
||||||
|
savePrefs()
|
||||||
|
})
|
||||||
$('#filter').addEventListener('change', e => { settings.filter = e.target.value; saveGlobal(); applyFilter() })
|
$('#filter').addEventListener('change', e => { settings.filter = e.target.value; saveGlobal(); applyFilter() })
|
||||||
$('#anim').value = settings.anim
|
$('#anim').value = settings.anim
|
||||||
$('#anim').addEventListener('change', e => { settings.anim = e.target.value; saveGlobal() })
|
$('#anim').addEventListener('change', e => { settings.anim = e.target.value; saveGlobal() })
|
||||||
|
|||||||
@@ -397,6 +397,19 @@ body.reading #netbanner { display: none; }
|
|||||||
.paged .curldrop { position: absolute; z-index: 2; pointer-events: none; opacity: 0; }
|
.paged .curldrop { position: absolute; z-index: 2; pointer-events: none; opacity: 0; }
|
||||||
.paged .curldrop.l { background: linear-gradient(90deg, rgba(0,0,0,.55), rgba(0,0,0,.2) 35%, rgba(0,0,0,0) 75%); }
|
.paged .curldrop.l { background: linear-gradient(90deg, rgba(0,0,0,.55), rgba(0,0,0,.2) 35%, rgba(0,0,0,0) 75%); }
|
||||||
.paged .curldrop.r { background: linear-gradient(270deg, rgba(0,0,0,.55), rgba(0,0,0,.2) 35%, rgba(0,0,0,0) 75%); }
|
.paged .curldrop.r { background: linear-gradient(270deg, rgba(0,0,0,.55), rgba(0,0,0,.2) 35%, rgba(0,0,0,0) 75%); }
|
||||||
|
/* Hendidura del lomo: la página se funde con el negro de detrás. Los puntos coinciden con gutter() en app.js. */
|
||||||
|
.paged:not(.fit-width) img[data-gutter] {
|
||||||
|
--gutter-stops: rgba(0,0,0,.4) 0, rgba(0,0,0,.6) 1.2%, rgba(0,0,0,.8) 3%, rgba(0,0,0,.93) 6%, #000 10%;
|
||||||
|
--gutter-mask: linear-gradient(var(--gutter-side), var(--gutter-stops));
|
||||||
|
-webkit-mask-image: var(--gutter-mask);
|
||||||
|
mask-image: var(--gutter-mask);
|
||||||
|
}
|
||||||
|
.paged:not(.fit-width) img[data-gutter="l"] { --gutter-side: to right; }
|
||||||
|
.paged:not(.fit-width) img[data-gutter="r"] { --gutter-side: to left; }
|
||||||
|
.paged:not(.fit-width) img[data-gutter="m"] {
|
||||||
|
--gutter-mask: linear-gradient(to right, #000 45%, rgba(0,0,0,.93) 47%, rgba(0,0,0,.8) 48.5%, rgba(0,0,0,.6) 49.4%, rgba(0,0,0,.4) 50%, rgba(0,0,0,.6) 50.6%, rgba(0,0,0,.8) 51.5%, rgba(0,0,0,.93) 53%, #000 55%);
|
||||||
|
}
|
||||||
|
.paged .leafkeepback { position: absolute; z-index: 2; pointer-events: none; background: #000; }
|
||||||
.paged .leafkeep { position: absolute; z-index: 2; pointer-events: none; max-width: none; max-height: none; object-fit: fill; }
|
.paged .leafkeep { position: absolute; z-index: 2; pointer-events: none; max-width: none; max-height: none; object-fit: fill; }
|
||||||
|
|
||||||
/* Info de la obra */
|
/* Info de la obra */
|
||||||
|
|||||||
Reference in New Issue
Block a user