Fix story page scrolling and ellipsis spacing
This commit is contained in:
@@ -12,6 +12,15 @@ introduction: |
|
|||||||
Now you stand beyond the wrought iron gate, with rain cooling your face and the hill rising before you.
|
Now you stand beyond the wrought iron gate, with rain cooling your face and the hill rising before you.
|
||||||
At its crest waits the old Victorian mansion, every dark window turned toward the path as if the building has been expecting you.
|
At its crest waits the old Victorian mansion, every dark window turned toward the path as if the building has been expecting you.
|
||||||
|
|
||||||
|
The gate gives under your hand with no protest, though its ironwork is wet enough to shine black.
|
||||||
|
Gravel shifts beneath your boots as you pass between the pillars, and the garden closes behind you with the soft finality of a curtain.
|
||||||
|
|
||||||
|
Halfway up the path, you stop and listen.
|
||||||
|
The rain has thinned to a whisper, but the house answers with other sounds: timber settling, gutters ticking, and something deep inside the walls that might be machinery or breath.
|
||||||
|
|
||||||
|
For a heartbeat you think the mansion is about to speak ... but only the wind moves through the ivy.
|
||||||
|
It drags the leaves across the brickwork in slow strokes, as if wiping dust from an old name.
|
||||||
|
|
||||||
# Room definitions
|
# Room definitions
|
||||||
rooms:
|
rooms:
|
||||||
# Starting area
|
# Starting area
|
||||||
@@ -22,6 +31,13 @@ rooms:
|
|||||||
The rain softens to a drizzle, and moonlight peeks through gaps in the clouds.
|
The rain softens to a drizzle, and moonlight peeks through gaps in the clouds.
|
||||||
Ancient oak trees frame the property, their branches swaying in the gentle breeze.
|
Ancient oak trees frame the property, their branches swaying in the gentle breeze.
|
||||||
At the top of three worn stone steps, the mansion's front door waits under a sagging porch roof.
|
At the top of three worn stone steps, the mansion's front door waits under a sagging porch roof.
|
||||||
|
The porch boards are swollen with rain, each one bending under your weight before it remembers its shape.
|
||||||
|
A brass knocker hangs at eye level, polished bright at the edges where countless hands have touched it and left no warmth behind.
|
||||||
|
The letter in your pocket presses against your ribs.
|
||||||
|
You remember the last line now: come before the clocks learn your name.
|
||||||
|
Somewhere above you, behind a blind upper window, a pale shape passes from left to right and is gone.
|
||||||
|
You tell yourself it was a reflection, then look back at the path and find no light behind you bright enough to make one.
|
||||||
|
The house waits.
|
||||||
When you reach for the handle, it turns before your fingers touch it, and the door opens {{sfx:squeaky-door.ogg}} with a long, complaining squeak.
|
When you reach for the handle, it turns before your fingers touch it, and the door opens {{sfx:squeaky-door.ogg}} with a long, complaining squeak.
|
||||||
exits:
|
exits:
|
||||||
- direction: north
|
- direction: north
|
||||||
|
|||||||
+12
-3
@@ -401,19 +401,26 @@ ol.choice {
|
|||||||
padding: 0 3rem 1rem 1rem;
|
padding: 0 3rem 1rem 1rem;
|
||||||
/* border: 1px dotted rgba(200,200,200,1); */
|
/* border: 1px dotted rgba(200,200,200,1); */
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
overflow-y: scroll;
|
overflow-y: auto;
|
||||||
opacity: 0.95;
|
opacity: 0.95;
|
||||||
mix-blend-mode: darken;
|
mix-blend-mode: darken;
|
||||||
}
|
}
|
||||||
|
|
||||||
#story {
|
#story {
|
||||||
overflow-x: visible;
|
overflow-x: visible;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-anchor: none;
|
||||||
text-align: justify;
|
text-align: justify;
|
||||||
text-justify: inter-word;
|
text-justify: inter-word;
|
||||||
margin-bottom: 1.2em;
|
margin-bottom: 0;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#paragraphs {
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-anchor: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* #story p span {
|
/* #story p span {
|
||||||
font-feature-settings: 'kern' on, 'liga' on, 'onum' on, 'clig' on, 'hlig' on;
|
font-feature-settings: 'kern' on, 'liga' on, 'onum' on, 'clig' on, 'hlig' on;
|
||||||
} */
|
} */
|
||||||
@@ -425,8 +432,10 @@ ol.choice {
|
|||||||
#page_right {
|
#page_right {
|
||||||
/* background-color: rgba(200,200,200,0.5); */
|
/* background-color: rgba(200,200,200,0.5); */
|
||||||
right: 7%;
|
right: 7%;
|
||||||
height: calc(28 * 1.45 * 1.2rem);
|
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
overflow-anchor: none;
|
||||||
/* transform: translateX(-1%) translateY(2%) rotateX(0deg) rotateY(-1deg) rotateZ(0deg); */
|
/* transform: translateX(-1%) translateY(2%) rotateX(0deg) rotateY(-1deg) rotateZ(0deg); */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ function kap(text, measureText, measure, hyphenation) {
|
|||||||
let spaceWidth = measureText('\u00A0');
|
let spaceWidth = measureText('\u00A0');
|
||||||
let nodes = [];
|
let nodes = [];
|
||||||
|
|
||||||
text.split(/([.,:;!?] |\s|\||<.*?>)/u).forEach(function (fragment) {
|
text.split(/([.,:;!?…] |\s|\||<.*?>)/u).forEach(function (fragment) {
|
||||||
let fragmentWidth = measureText(fragment);
|
let fragmentWidth = measureText(fragment);
|
||||||
|
|
||||||
if (fragment === ' ') {
|
if (fragment === ' ') {
|
||||||
@@ -21,8 +21,8 @@ function kap(text, measureText, measure, hyphenation) {
|
|||||||
nodes.push(linebreak.penalty(hyphenWidth * 0.25, 100, 1));
|
nodes.push(linebreak.penalty(hyphenWidth * 0.25, 100, 1));
|
||||||
} else if (fragment.match(/(<.*?>)/u)) {
|
} else if (fragment.match(/(<.*?>)/u)) {
|
||||||
nodes.push(linebreak.tag(fragmentWidth, fragment));
|
nodes.push(linebreak.tag(fragmentWidth, fragment));
|
||||||
} else if (fragment.match(/[.,:;!?] /u)) {
|
} else if (fragment.match(/[.,:;!?…] /u)) {
|
||||||
let punctuation = fragment.match(/([.,:;!?])( )/u);
|
let punctuation = fragment.match(/([.,:;!?…])( )/u);
|
||||||
let punctuationSymbolWidth = measureText(punctuation[1]) * 0.25;
|
let punctuationSymbolWidth = measureText(punctuation[1]) * 0.25;
|
||||||
let punctuationWidth = measureText(punctuation[1]) * 0.75 + spaceWidth;
|
let punctuationWidth = measureText(punctuation[1]) * 0.75 + spaceWidth;
|
||||||
nodes.push(linebreak.box(punctuationSymbolWidth, punctuation[1]));
|
nodes.push(linebreak.box(punctuationSymbolWidth, punctuation[1]));
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ class LayoutRendererModule extends BaseModule {
|
|||||||
|
|
||||||
if (node.type === 'box' && node.value !== '' && j < currentBreak.position) {
|
if (node.type === 'box' && node.value !== '' && j < currentBreak.position) {
|
||||||
const followsGlue = j > 0 && nodes[j - 1].type === 'glue';
|
const followsGlue = j > 0 && nodes[j - 1].type === 'glue';
|
||||||
const isTrailingPunctuation = /^[,.;:!?)]$/.test(node.value) && !followsGlue;
|
const isTrailingPunctuation = /^[,.;:!?…)]$/.test(node.value) && !followsGlue;
|
||||||
|
|
||||||
// Check if this box follows a penalty (hyphenation point)
|
// Check if this box follows a penalty (hyphenation point)
|
||||||
if (lastChild && isTrailingPunctuation) {
|
if (lastChild && isTrailingPunctuation) {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class UIDisplayHandlerModule extends BaseModule {
|
|||||||
'displayText',
|
'displayText',
|
||||||
'renderSentence',
|
'renderSentence',
|
||||||
'handleDeferredMediaBlock',
|
'handleDeferredMediaBlock',
|
||||||
|
'scrollStoryToEnd',
|
||||||
'rerenderStory',
|
'rerenderStory',
|
||||||
'clear',
|
'clear',
|
||||||
'scheduleRerender',
|
'scheduleRerender',
|
||||||
@@ -397,14 +398,11 @@ class UIDisplayHandlerModule extends BaseModule {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.scrollStoryToEnd(true);
|
||||||
|
|
||||||
// Start coordinated playback (animation + TTS), including chapter headings.
|
// Start coordinated playback (animation + TTS), including chapter headings.
|
||||||
await this.playbackCoordinator.play(sentence);
|
await this.playbackCoordinator.play(sentence);
|
||||||
|
|
||||||
// Scroll to bottom
|
|
||||||
if (this.pageRight) {
|
|
||||||
this.pageRight.scrollTop = this.pageRight.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call completion callback
|
// Call completion callback
|
||||||
if (sentence.onComplete) {
|
if (sentence.onComplete) {
|
||||||
sentence.onComplete();
|
sentence.onComplete();
|
||||||
@@ -464,6 +462,19 @@ class UIDisplayHandlerModule extends BaseModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scrollStoryToEnd(smooth = true) {
|
||||||
|
if (!this.pageRight) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
this.pageRight.scrollTo({
|
||||||
|
top: Math.max(0, this.pageRight.scrollHeight - this.pageRight.clientHeight),
|
||||||
|
behavior: smooth ? 'smooth' : 'auto'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async handleDeferredMediaBlock(sentence) {
|
async handleDeferredMediaBlock(sentence) {
|
||||||
document.dispatchEvent(new CustomEvent('story:media-block', {
|
document.dispatchEvent(new CustomEvent('story:media-block', {
|
||||||
detail: {
|
detail: {
|
||||||
@@ -496,6 +507,9 @@ class UIDisplayHandlerModule extends BaseModule {
|
|||||||
this.container.appendChild(this.paragraphContainer);
|
this.container.appendChild(this.paragraphContainer);
|
||||||
}
|
}
|
||||||
this.renderedItems = [];
|
this.renderedItems = [];
|
||||||
|
if (this.pageRight) {
|
||||||
|
this.pageRight.scrollTop = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user