Dimensiunea textului
20-48 px
Spațiul dintre rânduri
2,10
Tipografie
Font pentru lectură
Aa
@import url(‘https://fonts.googleapis.com/css2?family=Playwrite+RO:
[email protected]&display=swap’);
/* ==========================================================
BIBLIOTECA MEA
READER
========================================================== */
.bm-reader,
.bm-reader * {
box-sizing: border-box;
}
/* ==========================================================
CONTAINER PRINCIPAL
========================================================== */
.bm-reader {
–bm-ink: #111111;
–bm-title: #111111;
–bm-muted: #111111;
–bm-soft-text: #111111;
–bm-border: #e5e9f0;
–bm-border-strong: #d7ddea;
–bm-panel: #ffffff;
–bm-soft: #f8f9fb;
–bm-soft-blue: #f1f5ff;
–bm-soft-red: #fff1f2;
–bm-accent: #ef4452;
–bm-accent-dark: #dd3341;
–bm-navy: #12246b;
–bm-focus: #2657d8;
width: min(calc(100% – 32px), 1120px);
/* Panoul rămâne centrat pe telefon, tabletă, desktop și TV. */
max-width: 1120px;
margin: 16px auto;
overflow: hidden;
border: 1px solid var(–bm-border);
border-radius: 16px;
background: var(–bm-panel);
color: var(–bm-ink);
font-family: inherit;
box-shadow:
0 1px 2px rgba(16, 24, 40, .025),
0 5px 18px rgba(16, 24, 40, .035);
}
.bm-reader button,
.bm-reader input {
font: inherit;
}
.bm-reader button {
-webkit-tap-highlight-color: transparent;
}
/* ==========================================================
HEADER
========================================================== */
.bm-reader__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
padding:
12px
14px
9px;
}
.bm-reader__heading {
display: flex;
align-items: center;
gap: 9px;
min-width: 0;
}
.bm-reader__heading-icon {
width: 32px;
height: 32px;
flex:
0
0
32px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 10px;
background: var(–bm-soft-red);
color: var(–bm-accent);
}
.bm-reader__heading-icon svg {
width: 16px;
height: 16px;
}
.bm-reader__heading-copy {
min-width: 0;
}
.bm-reader__title {
margin:
0
0
2px
!important;
padding: 0 !important;
color: #111111 !important;
font-size: 18px !important;
line-height: 1.25 !important;
font-weight: 600 !important;
letter-spacing: -.015em;
}
.bm-reader__description {
margin: 0 !important;
padding: 0 !important;
color: #111111 !important;
font-size: 14px !important;
line-height: 1.45 !important;
font-weight: 400 !important;
}
/* ==========================================================
BUTON MAI MULTE OPȚIUNI
========================================================== */
.bm-reader__advanced-toggle {
appearance: none;
min-height: 32px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 7px;
flex:
0
0
auto;
margin: 0;
padding:
0
12px;
border:
1px
solid
#dedede;
border-radius: 999px;
background: #ffffff;
color: #111111 !important;
font-size: 15px;
line-height: 1.2;
font-weight: 500;
cursor: pointer;
transition:
background-color .16s ease,
border-color .16s ease,
transform .16s ease;
}
.bm-reader__advanced-toggle:hover {
background: #f7f7f7;
border-color: #cfcfcf;
}
.bm-reader__advanced-toggle:active {
transform: scale(.985);
}
.bm-reader__advanced-arrow {
width: 7px;
height: 7px;
border-right:
1.6px
solid
currentColor;
border-bottom:
1.6px
solid
currentColor;
transform:
rotate(45deg)
translateY(-2px);
transition:
transform
.2s
ease;
}
.bm-reader__advanced-toggle[aria-expanded=”true”]
.bm-reader__advanced-arrow {
transform:
rotate(225deg)
translate(-1px, -1px);
}
/* ==========================================================
ZONA PRINCIPALĂ
========================================================== */
.bm-reader__primary {
display: grid;
grid-template-columns:
minmax(180px, .9fr)
minmax(250px, 1.25fr)
minmax(300px, 1.35fr);
gap: 7px;
padding:
0
14px
13px;
}
.bm-reader__primary-card {
min-width: 0;
min-height: 72px;
display: flex;
flex-direction: column;
justify-content: center;
padding:
9px
10px;
border:
1px
solid
var(–bm-border);
border-radius: 12px;
background: #ffffff;
}
.bm-reader__control-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 6px;
}
.bm-reader__control-title {
color: #111111 !important;
font-size: 13px;
line-height: 1.3;
font-weight: 600;
}
.bm-reader__control-hint,
.bm-reader__range-value {
color: #111111 !important;
font-size: 12px;
line-height: 1.2;
font-weight: 500;
}
/* ==========================================================
FONT SIZE STEPPER
========================================================== */
.bm-reader__stepper {
height: 32px;
display: grid;
grid-template-columns:
36px
1fr
36px;
overflow: hidden;
border:
1px
solid
var(–bm-border-strong);
border-radius: 9px;
background: #ffffff;
}
.bm-reader__stepper button {
appearance: none;
margin: 0;
padding: 0;
border: 0;
background: transparent;
color: #111111;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition:
background-color
.15s
ease;
}
.bm-reader__stepper button:hover {
background: var(–bm-soft);
}
.bm-reader__stepper button + button {
border-left:
1px
solid
var(–bm-border);
}
.bm-reader__stepper-value {
font-size: 14px !important;
font-weight: 600 !important;
}
/* ==========================================================
LINE HEIGHT
========================================================== */
.bm-reader__range-wrap {
height: 32px;
display: flex;
align-items: center;
gap: 8px;
padding:
0
9px;
border:
1px
solid
var(–bm-border-strong);
border-radius: 9px;
background: #ffffff;
}
.bm-reader__range-icon {
display: inline-flex;
color: #7b86a0;
}
.bm-reader__range-icon svg {
width: 15px;
height: 15px;
}
.bm-reader__range-wrap input[type=”range”] {
width: 100%;
min-width: 80px;
margin: 0;
accent-color: var(–bm-accent);
cursor: pointer;
}
/* ==========================================================
ACȚIUNI RAPIDE
========================================================== */
.bm-reader__quick-actions {
min-width: 0;
display: grid;
grid-template-columns:
repeat(3, minmax(0, 1fr));
gap: 6px;
}
.bm-reader__quick {
appearance: none;
min-width: 0;
min-height: 72px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 5px;
margin: 0;
padding:
7px
6px;
border:
1px
solid
var(–bm-border);
border-radius: 12px;
background: #ffffff;
color: #111111 !important;
font-size: 16px;
line-height: 1.3;
font-weight: 500;
text-align: center;
cursor: pointer;
transition:
background-color .15s ease,
border-color .15s ease,
color .15s ease,
transform .15s ease;
}
.bm-reader__quick:hover {
background: var(–bm-soft);
}
.bm-reader__quick:active {
transform: scale(.985);
}
.bm-reader__quick[aria-pressed=”true”] {
border-color: var(–bm-navy);
background: var(–bm-navy);
color: #ffffff !important;
}
.bm-reader__quick–reset {
color: #111111 !important;
}
.bm-reader__quick–reset:hover {
background: #fff7f8;
border-color: #f0c6ca;
}
.bm-reader__quick-icon {
height: 17px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.bm-reader__quick-icon–syllable {
font-size: 13px;
font-weight: 700;
letter-spacing: -.02em;
}
.bm-reader__quick-icon–bionic {
font-size: 18px;
font-weight: 700;
}
/* ==========================================================
ADVANCED PANEL
========================================================== */
.bm-reader__advanced {
border-top:
1px
solid
var(–bm-border);
background: #fbfcfe;
animation:
bmReaderOpen
.2s
ease;
}
.bm-reader__advanced[hidden] {
display: none !important;
}
.bm-reader__advanced-inner {
padding:
14px
14px
15px;
}
@keyframes bmReaderOpen {
from {
opacity: 0;
transform:
translateY(-4px);
}
to {
opacity: 1;
transform:
translateY(0);
}
}
/* ==========================================================
HEADINGS ADVANCED
========================================================== */
.bm-reader__section-head,
.bm-reader__fonts-head {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 12px;
margin-bottom: 9px;
}
.bm-reader__section-head–tools {
margin-bottom: 8px;
}
.bm-reader__eyebrow {
display: block;
margin-bottom: 2px;
color: var(–bm-accent);
font-size: 10px;
line-height: 1.3;
font-weight: 700;
letter-spacing: .08em;
text-transform: uppercase;
}
.bm-reader__section-title {
margin: 0 !important;
padding: 0 !important;
color: #111111 !important;
font-size: 17px !important;
line-height: 1.3 !important;
font-weight: 600 !important;
letter-spacing: -.01em;
}
.bm-reader__section-note {
color: #111111 !important;
font-size: 9px;
line-height: 1.35;
white-space: nowrap;
}
/* ==========================================================
DIVIDER
========================================================== */
.bm-reader__divider {
height: 1px;
margin:
13px
0;
background: var(–bm-border);
}
/* ==========================================================
INSTRUMENTE
========================================================== */
.bm-reader__tools-grid {
display: grid;
grid-template-columns:
repeat(4, minmax(0, 1fr));
gap: 6px;
}
.bm-reader__tool {
appearance: none;
min-width: 0;
min-height: 72px;
display: grid;
grid-template-columns:
29px
minmax(0, 1fr)
27px;
align-items: center;
gap: 7px;
margin: 0;
padding: 8px;
border:
1px
solid
var(–bm-border);
border-radius: 11px;
background: #ffffff;
color: #111111 !important;
text-align: left;
cursor: pointer;
transition:
background-color .15s ease,
border-color .15s ease,
box-shadow .15s ease;
}
.bm-reader__tool:hover {
background: #fcfdff;
border-color: #d9dfeb;
}
.bm-reader__tool[aria-pressed=”true”] {
border-color: #bcc7e4;
background: #f7f9ff;
}
.bm-reader__tool-icon {
width: 29px;
height: 29px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 8px;
background: var(–bm-soft-blue);
color: var(–bm-navy);
}
.bm-reader__tool-icon svg {
width: 16px;
height: 16px;
}
.bm-reader__tool-icon–letters {
font-size: 10px;
font-weight: 700;
letter-spacing: -.03em;
}
.bm-reader__tool-copy {
min-width: 0;
display: flex;
flex-direction: column;
gap: 1px;
}
.bm-reader__tool-copy strong {
color: #111111 !important;
font-size: 13px;
line-height: 1.3;
font-weight: 600;
}
.bm-reader__tool-copy small {
color: #111111 !important;
font-size: 11px;
line-height: 1.4;
font-weight: 400;
}
/* ==========================================================
SWITCH
========================================================== */
.bm-reader__switch {
position: relative;
width: 26px;
height: 15px;
flex:
0
0
26px;
border-radius: 999px;
background: #d9dee8;
transition:
background-color
.18s
ease;
}
.bm-reader__switch::after {
content: “”;
position: absolute;
top: 2px;
left: 2px;
width: 11px;
height: 11px;
border-radius: 50%;
background: #ffffff;
box-shadow:
0
1px
2px
rgba(16, 24, 40, .18);
transition:
transform
.18s
ease;
}
.bm-reader__tool[aria-pressed=”true”]
.bm-reader__switch {
background: var(–bm-navy);
}
.bm-reader__tool[aria-pressed=”true”]
.bm-reader__switch::after {
transform:
translateX(11px);
}
/* ==========================================================
FONTURI
========================================================== */
.bm-reader__fonts-head {
align-items: center;
margin-bottom: 8px;
}
.bm-reader__fonts-aa {
width: 31px;
height: 31px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 9px;
background: var(–bm-soft-red);
color: var(–bm-accent);
font-size: 12px;
font-weight: 700;
}
.bm-reader__font-grid {
display: grid;
grid-template-columns:
repeat(4, minmax(0, 1fr));
gap: 5px;
}
.bm-reader__font {
appearance: none;
min-width: 0;
min-height: 58px;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
gap: 2px;
margin: 0;
padding:
7px
8px;
border:
1px
solid
var(–bm-border);
border-radius: 10px;
background: #ffffff;
color: #111111 !important;
text-align: left;
cursor: pointer;
transition:
background-color .15s ease,
border-color .15s ease,
color .15s ease,
transform .15s ease;
}
.bm-reader__font:hover {
background: var(–bm-soft);
transform:
translateY(-1px);
}
.bm-reader__font.is-active {
border-color: var(–bm-navy);
background: var(–bm-navy);
color: #ffffff !important;
}
.bm-reader__font strong {
color: inherit;
font-size: 13px;
line-height: 1.3;
font-weight: 600;
}
.bm-reader__font span {
color: inherit;
opacity: .82;
font-size: 12px;
line-height: 1.3;
}
.bm-reader__font[data-bm-font=”lexend”] {
font-family:
Lexend,
Arial,
sans-serif;
}
.bm-reader__font[data-bm-font=”literata”] {
font-family:
Literata,
Georgia,
serif;
}
.bm-reader__font[data-bm-font=”playwrite”] {
font-family:
“Playwrite RO”,
cursive;
}
/* ==========================================================
TEXTUL PAGINII
========================================================== */
[data-bm-reading-content] {
–bm-reader-font-size: 34px;
–bm-reader-line-height: 2.10;
–bm-reader-width: 760px;
width:
min(
100%,
var(–bm-reader-width)
);
margin-left: auto;
margin-right: auto;
font-size:
var(–bm-reader-font-size)
!important;
line-height:
var(–bm-reader-line-height)
!important;
transition:
width .2s ease,
max-width .2s ease,
font-size .2s ease,
line-height .2s ease,
background-color .2s ease,
color .2s ease;
}
[data-bm-reading-content] p,
[data-bm-reading-content] li,
[data-bm-reading-content] blockquote {
font-size:
inherit
!important;
line-height:
inherit
!important;
}
[data-bm-auto-reading-content] {
display: flow-root;
}
[data-bm-auto-reading-content] > :first-child {
margin-top: 0;
}
[data-bm-auto-reading-content] > :last-child {
margin-bottom: 0;
}
/* ==========================================================
TEMELE TEXTULUI
========================================================== */
[data-bm-reading-content].bm-reader-theme-light {
color: #15171b;
background: #ffffff;
}
[data-bm-reading-content].bm-reader-theme-warm {
color: #2a2118;
background: #fff8e9;
}
[data-bm-reading-content].bm-reader-theme-gray {
color: #1f2328;
background: #f1f3f5;
}
[data-bm-reading-content].bm-reader-theme-dark {
color: #f5f7fa;
background: #17191f;
}
[data-bm-reading-content].bm-reader-theme-warm,
[data-bm-reading-content].bm-reader-theme-gray,
[data-bm-reading-content].bm-reader-theme-dark {
padding: 30px;
border-radius: 18px;
}
[data-bm-reading-content].bm-reader-bold {
font-weight: 650;
}
[data-bm-reading-content].bm-reader-uppercase {
text-transform: uppercase;
}
.bm-reader-bionic {
font-weight: 780;
}
.bm-reader-syllable–1 {
color: #d83b47;
}
.bm-reader-syllable–2 {
color: #245bb8;
}
/* ==========================================================
LINIE DE CITIRE
========================================================== */
[data-bm-reading-content].bm-reader-line-enabled p,
[data-bm-reading-content].bm-reader-line-enabled li,
[data-bm-reading-content].bm-reader-line-enabled blockquote {
position: relative;
border-radius: 7px;
transition:
background-color
.12s
ease;
}
[data-bm-reading-content].bm-reader-line-enabled
.bm-reader-line-active {
background:
rgba(255, 220, 70, .24);
}
/* ==========================================================
WORDPRESS DIRECT TARGETS
========================================================== */
[data-bm-reading-target] {
transition:
font-size .18s ease,
line-height .18s ease,
color .18s ease,
background-color .18s ease,
max-width .18s ease;
}
[data-bm-reading-target].bm-reader-line-active {
background-image:
linear-gradient(
rgba(255, 220, 70, .24),
rgba(255, 220, 70, .24)
);
border-radius: 5px;
}
/* ==========================================================
FOCUS
========================================================== */
.bm-reader button:focus-visible,
.bm-reader input:focus-visible {
outline:
2px
solid
var(–bm-focus);
outline-offset: 2px;
}
.bm-reader button:disabled {
opacity: .38;
cursor: not-allowed;
}
/* ==========================================================
TABLET
========================================================== */
/* TV / ecrane foarte late */
@media (min-width: 1440px) {
.bm-reader {
width: min(calc(100% – 64px), 1180px);
max-width: 1180px;
}
}
@media (max-width: 1050px) {
.bm-reader {
width:
calc(
100% – 28px
);
}
.bm-reader__primary {
grid-template-columns:
1fr
1.35fr;
}
.bm-reader__quick-actions {
grid-column:
1
/
-1;
}
.bm-reader__quick {
min-height: 50px;
flex-direction: row;
}
.bm-reader__tools-grid {
grid-template-columns:
repeat(
2,
minmax(0, 1fr)
);
}
.bm-reader__font-grid {
grid-template-columns:
repeat(
4,
minmax(0, 1fr)
);
}
}
/* ==========================================================
MOBILE
========================================================== */
@media (max-width: 700px) {
.bm-reader {
width:
calc(
100% – 20px
);
margin:
12px
auto;
border-radius: 14px;
}
.bm-reader__header {
align-items: flex-start;
padding:
11px
12px
8px;
}
.bm-reader__heading-icon {
width: 30px;
height: 30px;
flex-basis: 30px;
}
.bm-reader__advanced-toggle {
min-height: 31px;
}
.bm-reader__primary {
grid-template-columns:
1fr;
padding:
0
12px
12px;
gap: 6px;
}
.bm-reader__primary-card {
min-height: 60px;
}
.bm-reader__quick-actions {
grid-column: auto;
}
.bm-reader__advanced-inner {
padding: 12px;
}
.bm-reader__section-note {
display: none;
}
.bm-reader__font-grid {
grid-template-columns:
repeat(
3,
minmax(0, 1fr)
);
}
}
/* ==========================================================
TELEFON MIC
========================================================== */
@media (max-width: 520px) {
.bm-reader {
width:
calc(
100% – 16px
);
}
.bm-reader__header {
flex-direction: column;
gap: 9px;
}
.bm-reader__advanced-toggle {
width: 100%;
}
.bm-reader__quick-actions {
grid-template-columns:
1fr;
}
.bm-reader__quick {
min-height: 42px;
flex-direction: row;
}
.bm-reader__tools-grid {
grid-template-columns:
1fr;
}
.bm-reader__font-grid {
grid-template-columns:
repeat(
2,
minmax(0, 1fr)
);
}
[data-bm-reading-content].bm-reader-theme-warm,
[data-bm-reading-content].bm-reader-theme-gray,
[data-bm-reading-content].bm-reader-theme-dark {
padding: 20px;
}
}
/* ==========================================================
TELEFON FOARTE MIC
========================================================== */
@media (max-width: 390px) {
.bm-reader {
width: calc(100% – 12px);
margin: 8px auto;
border-radius: 12px;
}
.bm-reader__advanced-inner {
padding: 10px;
}
}
/* ==========================================================
REDUCED MOTION
========================================================== */
@media (prefers-reduced-motion: reduce) {
.bm-reader *,
[data-bm-reading-content] {
animation: none !important;
transition: none !important;
scroll-behavior: auto !important;
}
}
/* ==========================================================
TYPOGRAPHY COHERENCE
Text mai lizibil și coerent cu blocul introductiv al paginii
========================================================== */
@media (max-width: 700px) {
.bm-reader__title {
font-size: 17px !important;
}
.bm-reader__description {
font-size: 13px !important;
}
.bm-reader__advanced-toggle {
font-size: 14px;
}
.bm-reader__quick {
font-size: 15px;
}
.bm-reader__section-title {
font-size: 16px !important;
}
.bm-reader__tool-copy strong {
font-size: 13px;
}
.bm-reader__tool-copy small {
font-size: 11px;
}
}
(function () {
“use strict”;
var STORAGE_KEY =
“biblioteca-mea-reader-v7”;
var DEFAULTS = {
fontSize: 34,
lineHeight: 2.10,
syllables: false,
bionic: false,
theme: “system”,
width: “medium”,
align: “left”,
coloredSyllables: false,
readingLine: false,
boldText: false,
uppercase: false,
font: “lexend”
};
var MIN_FONT_SIZE = 20;
var MAX_FONT_SIZE = 48;
var FONT_STEP = 2;
var WORD_RX =
/[A-Za-zĂÂÎȘȚăâîșț]+(?:-[A-Za-zĂÂÎȘȚăâîșț]+)*/g;
var WORD_ONLY_RX =
/^[A-Za-zĂÂÎȘȚăâîșț]+(?:-[A-Za-zĂÂÎȘȚăâîșț]+)*$/;
/* ======================================================
DEFAULTS
====================================================== */
function cloneDefaults() {
return Object.assign(
{},
DEFAULTS
);
}
/* ======================================================
LOAD SETTINGS
====================================================== */
function loadSettings() {
try {
var stored =
localStorage.getItem(
STORAGE_KEY
);
var settings =
stored
? Object.assign(
{},
DEFAULTS,
JSON.parse(stored)
)
: cloneDefaults();
if (
settings.font === “noto” ||
settings.font === “source” ||
settings.font === “roboto”
) {
settings.font =
“lexend”;
}
return settings;
} catch (error) {
return cloneDefaults();
}
}
/* ======================================================
SAVE SETTINGS
====================================================== */
function saveSettings(state) {
try {
localStorage.setItem(
STORAGE_KEY,
JSON.stringify(state)
);
} catch (error) {}
}
/* ======================================================
DOM POSITION
====================================================== */
function isAfter(reference, node) {
return !!(
reference.compareDocumentPosition(node) &
Node.DOCUMENT_POSITION_FOLLOWING
);
}
/* ======================================================
EXCLUDED CONTENT
====================================================== */
function isExcludedTextElement(element) {
if (
!element ||
element.nodeType !== 1
) {
return true;
}
if (
element.closest(
“[data-bm-reader]”
)
) {
return true;
}
if (
element.closest(
“header, nav, footer, aside”
)
) {
return true;
}
if (
element.closest(
“.entry-footer, ” +
“.post-footer, ” +
“.post-navigation, ” +
“.navigation, ” +
“.author-box, ” +
“.author-bio, ” +
“.related-posts, ” +
“.related, ” +
“.comments-area, ” +
“.post-comments, ” +
“.share-buttons, ” +
“.social-share, ” +
“.site-footer, ” +
“.wp-block-post-navigation-link, ” +
“.wp-block-query, ” +
“.wp-block-latest-posts”
)
) {
return true;
}
return false;
}
/* ======================================================
TEXT LENGTH
====================================================== */
function readableEnough(element) {
var text =
(
element.textContent ||
“”
)
.replace(
/s+/g,
” ”
)
.trim();
return text.length >= 2;
}
/* ======================================================
FIND TEXT AFTER READER PANEL
====================================================== */
function discoverTargets(panel) {
var customTarget =
panel.getAttribute(
“data-bm-target”
);
var scope = null;
var candidates = [];
/*
* Permite și selectarea manuală a textului.
*/
if (customTarget) {
try {
scope =
document.querySelector(
customTarget
);
} catch (error) {
scope = null;
}
if (scope) {
if (
scope.matches &&
scope.matches(
“p, li, blockquote”
)
) {
candidates.push(
scope
);
}
candidates =
candidates.concat(
Array.prototype.slice.call(
scope.querySelectorAll(
“p, li, blockquote”
)
)
);
return candidates.filter(
function (element) {
return (
!isExcludedTextElement(
element
) &&
readableEnough(
element
)
);
}
);
}
}
/*
* Preferăm articolul în care se află panoul.
*/
var article =
panel.closest(
“article”
);
if (article) {
scope = article;
} else {
scope =
panel.closest(
“.entry-content, ” +
“.post-content, ” +
“.single-content, ” +
“.wp-block-post-content, ” +
“.elementor-widget-theme-post-content, ” +
“main”
) ||
document.body;
}
candidates =
Array.prototype.slice.call(
scope.querySelectorAll(
“p, li, blockquote”
)
);
candidates =
candidates.filter(
function (element) {
if (
!isAfter(
panel,
element
)
) {
return false;
}
if (
isExcludedTextElement(
element
)
) {
return false;
}
if (
!readableEnough(
element
)
) {
return false;
}
if (article) {
var ownerArticle =
element.closest(
“article”
);
if (
ownerArticle &&
ownerArticle !== article
) {
return false;
}
}
return true;
}
);
/*
* Fallback pentru unele page buildere.
*/
if (
!candidates.length
) {
candidates =
Array.prototype.slice.call(
document.querySelectorAll(
“p, li, blockquote”
)
)
.filter(
function (element) {
return (
isAfter(
panel,
element
) &&
!isExcludedTextElement(
element
) &&
readableEnough(
element
)
);
}
);
}
return candidates;
}
/* ======================================================
TEXT NODES
====================================================== */
function getTextNodes(container) {
var nodes = [];
var walker =
document.createTreeWalker(
container,
NodeFilter.SHOW_TEXT,
{
acceptNode:
function (node) {
if (
!node.nodeValue ||
!node.nodeValue.trim()
) {
return NodeFilter.FILTER_REJECT;
}
var parent =
node.parentElement;
if (!parent) {
return NodeFilter.FILTER_REJECT;
}
if (
parent.closest(
“script, style, code, pre, textarea, button, a”
)
) {
return NodeFilter.FILTER_REJECT;
}
return NodeFilter.FILTER_ACCEPT;
}
}
);
while (
walker.nextNode()
) {
nodes.push(
walker.currentNode
);
}
return nodes;
}
/* ======================================================
SILABIFICARE
====================================================== */
function syllabifyWord(word) {
/*
* Silabificarea limbii române nu poate fi rezolvată corect
* doar prin regula „taie după fiecare vocală”.
*
* Pentru formele frecvente din textele pentru copii folosim
* un dicționar explicit, iar pentru restul cuvintelor păstrăm
* un fallback conservator.
*
* IMPORTANT:
* – alternarea culorilor se resetează pentru fiecare cuvânt;
* – formele cu cratimă pot rămâne în aceeași silabă:
* mi-ne-n, nu mi-ne-n împărțit ca trei cuvinte.
*/
var original = word;
var lower = word.toLocaleLowerCase(“ro-RO”);
var known = {
/* Avion cu motor */
“avion”: [“a”, “vi”, “on”],
“motor”: [“mo”, “tor”],
/* Ia-mă și pe mine-n zbor */
“ia-mă”: [“ia”, “mă”],
“mine-n”: [“mi”, “ne-n”],
/* Nu te iau, că ești mic */
“nu”: [“nu”],
“te”: [“te”],
“iau”: [“iau”],
“că”: [“că”],
“ești”: [“ești”],
“mic”: [“mic”],
/* Și te cheamă Polonic */
“și”: [“și”],
“cheamă”: [“chea”, “mă”],
“polonic”: [“po”, “lo”, “nic”],
/* Cuvinte monosilabice din poezie */
“cu”: [“cu”],
“pe”: [“pe”],
“zbor”: [“zbor”]
};
function restoreCase(parts) {
/*
* Păstrează majuscula inițială:
* avion -> a-vi-on
* Avion -> A-vi-on
* Polonic -> Po-lo-nic
*/
if (
original &&
original.charAt(0) ===
original.charAt(0).toLocaleUpperCase(“ro-RO”) &&
original.charAt(0) !==
original.charAt(0).toLocaleLowerCase(“ro-RO”)
) {
parts[0] =
parts[0].charAt(0).toLocaleUpperCase(“ro-RO”) +
parts[0].slice(1);
}
return parts;
}
if (known[lower]) {
return restoreCase(
known[lower].slice()
);
}
/*
* Dacă este o formă cu cratimă pe care nu o cunoaștem,
* nu rupem arbitrar cratima. Procesăm fiecare segment și
* păstrăm cratima lipită de segmentul următor.
*/
if (lower.indexOf(“-“) !== -1) {
var hyphenParts =
original.split(“-“);
var result = [];
hyphenParts.forEach(
function (part, partIndex) {
if (!part) {
return;
}
var partSyllables =
syllabifyWord(part);
partSyllables.forEach(
function (syllable, syllableIndex) {
if (
partIndex > 0 &&
syllableIndex === 0 &&
result.length
) {
result[result.length – 1] +=
“-” + syllable;
} else {
result.push(
syllable
);
}
}
);
}
);
return result.length
? result
: [original];
}
/*
* Cuvintele foarte scurte rămân întregi.
* Este preferabil să nu colorăm greșit un cuvânt decât
* să inventăm o despărțire.
*/
if (word.length <= 3) {
return [word];
}
var vowels =
"aeiouăâîAEIOUĂÂÎ";
var syllables = [];
var current = "";
var i;
/*
* Fallback simplificat:
* găsește nucleele vocalice și mută o consoană dintre
* două nuclee la silaba următoare.
*
* Nu este un dicționar fonetic complet al limbii române,
* de aceea excepțiile pedagogice importante rămân în
* obiectul `known` de mai sus.
*/
for (
i = 0;
i < word.length;
i++
) {
current +=
word[i];
if (
vowels.indexOf(
word[i]
) === -1
) {
continue;
}
var nextVowelIndex =
-1;
var j;
for (
j = i + 1;
j consoana începe de regulă silaba următoare:
* mo-tor, po-lo-nic
*/
syllables.push(
current
);
current = “”;
} else {
/*
* Pentru grupuri de două sau mai multe consoane,
* păstrăm prima consoană în silaba precedentă.
* Cazurile care nu urmează această regulă trebuie
* adăugate în dicționarul `known`.
*/
current +=
word[i + 1] ||
“”;
i++;
syllables.push(
current
);
current = “”;
}
}
if (current) {
syllables.push(
current
);
}
/*
* Protecție: funcția nu are voie să piardă sau să dubleze
* litere. Dacă ceva nu se potrivește, păstrăm cuvântul întreg.
*/
if (
syllables.join(“”).length !==
word.length
) {
return [word];
}
return syllables.length
? syllables
: [word];
}
/* ======================================================
PE SILABE
====================================================== */
function applySyllables(container) {
getTextNodes(
container
)
.forEach(
function (node) {
node.nodeValue =
node.nodeValue.replace(
WORD_RX,
function (word) {
if (word.toLocaleLowerCase(“ro-RO”) === “scoate”) {
return word.charAt(0) === word.charAt(0).toLocaleUpperCase(“ro-RO”)
? “Scou00E1u00A0u2022u00A0te”
: “scou00E1u00A0u2022u00A0te”;
}
return syllabifyWord(word).join(“-“);
}
);
}
);
}
/* ======================================================
SILABE COLORATE
====================================================== */
function applyColoredSyllables(container) {
getTextNodes(
container
)
.forEach(
function (node) {
var fragment =
document.createDocumentFragment();
var pieces =
node.nodeValue.split(
/([A-Za-zĂÂÎȘȚăâîșț]+(?:-[A-Za-zĂÂÎȘȚăâîșț]+)*)/g
);
pieces.forEach(
function (piece) {
if (
WORD_ONLY_RX.test(
piece
)
) {
syllabifyWord(
piece
)
.forEach(
function (
syllable,
index
) {
var span =
document.createElement(
“span”
);
/* Culoarea începe din nou cu roșu la fiecare cuvânt. */
span.className =
index % 2 === 0
? “bm-reader-syllable–1”
: “bm-reader-syllable–2”;
span.textContent =
syllable;
fragment.appendChild(
span
);
}
);
} else {
fragment.appendChild(
document.createTextNode(
piece
)
);
}
}
);
if (
node.parentNode
) {
node.parentNode.replaceChild(
fragment,
node
);
}
}
);
}
/* ======================================================
BIONIC READING
====================================================== */
function applyBionic(container) {
getTextNodes(
container
)
.forEach(
function (node) {
var fragment =
document.createDocumentFragment();
var pieces =
node.nodeValue.split(
/([A-Za-zĂÂÎȘȚăâîșț]{3,})/g
);
pieces.forEach(
function (piece) {
if (
/^[A-Za-zĂÂÎȘȚăâîșț]{3,}$/.test(
piece
)
) {
var boldLength =
Math.max(
1,
Math.ceil(
piece.length *
.45
)
);
var strong =
document.createElement(
“span”
);
strong.className =
“bm-reader-bionic”;
strong.textContent =
piece.slice(
0,
boldLength
);
fragment.appendChild(
strong
);
fragment.appendChild(
document.createTextNode(
piece.slice(
boldLength
)
)
);
} else {
fragment.appendChild(
document.createTextNode(
piece
)
);
}
}
);
if (
node.parentNode
) {
node.parentNode.replaceChild(
fragment,
node
);
}
}
);
}
/* ======================================================
FONT FAMILY
====================================================== */
function getFontFamily(font) {
var fonts = {
roboto:
“Roboto, Arial, sans-serif”,
lexend:
“Lexend, Arial, sans-serif”,
atkinson:
‘”Atkinson Hyperlegible”, Arial, sans-serif’,
literata:
“Literata, Georgia, serif”,
playwrite:
‘”Playwrite RO”, cursive’,
bionic:
“Roboto, Arial, sans-serif”
};
return (
fonts[font] ||
fonts.roboto
);
}
/* ======================================================
INIT READER
====================================================== */
function initReader(
panel,
index
) {
var advanced =
panel.querySelector(
“[data-bm-advanced]”
);
var advancedToggle =
panel.querySelector(
“[data-bm-advanced-toggle]”
);
var advancedLabel =
panel.querySelector(
“[data-bm-advanced-label]”
);
var fontMinus =
panel.querySelector(
“[data-bm-font-minus]”
);
var fontPlus =
panel.querySelector(
“[data-bm-font-plus]”
);
var fontDefault =
panel.querySelector(
“[data-bm-font-default]”
);
var fontValue =
panel.querySelector(
“[data-bm-font-value]”
);
var lineInput =
panel.querySelector(
“[data-bm-line-height]”
);
var lineValue =
panel.querySelector(
“[data-bm-line-value]”
);
var syllablesButton =
panel.querySelector(
“[data-bm-syllables]”
);
var bionicButton =
panel.querySelector(
“[data-bm-bionic]”
);
var resetButton =
panel.querySelector(
“[data-bm-reset]”
);
var state =
loadSettings();
var advancedId =
“bm-reader-advanced-” +
index;
var targets = [];
var originalHTML =
new Map();
if (advanced) {
advanced.id =
advancedId;
}
if (
advancedToggle
) {
advancedToggle.setAttribute(
“aria-controls”,
advancedId
);
}
panel.setAttribute(
“data-bm-js”,
“ready”
);
/* ==================================================
TARGETS
================================================== */
function ensureTargets() {
var found =
discoverTargets(
panel
);
found.forEach(
function (element) {
if (
!originalHTML.has(
element
)
) {
originalHTML.set(
element,
element.innerHTML
);
element.setAttribute(
“data-bm-reading-target”,
“”
);
}
}
);
targets =
found;
panel.setAttribute(
“data-bm-target-count”,
String(
targets.length
)
);
return (
targets.length > 0
);
}
/* ==================================================
REMOVE READING LINE
================================================== */
function removeReadingLine() {
targets.forEach(
function (element) {
element.classList.remove(
“bm-reader-line-active”
);
}
);
}
/* ==================================================
UPDATE UI
================================================== */
function updateUI() {
if (
fontValue
) {
fontValue.textContent =
state.fontSize +
” px”;
}
if (
fontMinus
) {
fontMinus.disabled =
state.fontSize =
MAX_FONT_SIZE;
}
if (
lineInput
) {
lineInput.value =
String(
state.lineHeight
);
}
if (
lineValue
) {
lineValue.textContent =
Number(
state.lineHeight
)
.toFixed(2)
.replace(
“.”,
“,”
);
}
if (
syllablesButton
) {
syllablesButton.setAttribute(
“aria-pressed”,
state.syllables
? “true”
: “false”
);
}
if (
bionicButton
) {
bionicButton.setAttribute(
“aria-pressed”,
state.bionic
? “true”
: “false”
);
}
panel.querySelectorAll(
“[data-bm-theme]”
)
.forEach(
function (button) {
button.classList.toggle(
“is-active”,
button.getAttribute(
“data-bm-theme”
) ===
state.theme
);
}
);
panel.querySelectorAll(
“[data-bm-width]”
)
.forEach(
function (button) {
button.classList.toggle(
“is-active”,
button.getAttribute(
“data-bm-width”
) ===
state.width
);
}
);
panel.querySelectorAll(
“[data-bm-align]”
)
.forEach(
function (button) {
button.classList.toggle(
“is-active”,
button.getAttribute(
“data-bm-align”
) ===
state.align
);
}
);
panel.querySelectorAll(
“[data-bm-tool]”
)
.forEach(
function (button) {
var key =
button.getAttribute(
“data-bm-tool”
);
button.setAttribute(
“aria-pressed”,
state[key]
? “true”
: “false”
);
}
);
panel.querySelectorAll(
“[data-bm-font]”
)
.forEach(
function (button) {
button.classList.toggle(
“is-active”,
button.getAttribute(
“data-bm-font”
) ===
state.font
);
}
);
}
/* ==================================================
RESTORE ORIGINAL TEXT
================================================== */
function restoreOriginalText() {
targets.forEach(
function (element) {
if (
originalHTML.has(
element
)
) {
element.innerHTML =
originalHTML.get(
element
);
}
}
);
}
/* ==================================================
APPLY CONTENT SETTINGS
================================================== */
function applyContentSettings() {
if (
!ensureTargets()
) {
return;
}
restoreOriginalText();
var widths = {
narrow:
“620px”,
medium:
“760px”,
wide:
“980px”
};
var maxWidth =
widths[
state.width
] ||
widths.medium;
var resolvedTheme =
state.theme;
if (
resolvedTheme ===
“system”
) {
resolvedTheme =
window.matchMedia &&
window.matchMedia(
“(prefers-color-scheme: dark)”
)
.matches
? “dark”
: “light”;
}
targets.forEach(
function (element) {
element.style.setProperty(
“font-size”,
state.fontSize +
“px”,
“important”
);
element.style.setProperty(
“line-height”,
String(
state.lineHeight
),
“important”
);
element.style.setProperty(
“font-family”,
getFontFamily(
state.font
),
“important”
);
element.style.setProperty(
“text-align”,
state.align,
“important”
);
element.style.setProperty(
“max-width”,
maxWidth,
“important”
);
element.style.setProperty(
“margin-left”,
“auto”,
“important”
);
element.style.setProperty(
“margin-right”,
“auto”,
“important”
);
if (
state.boldText
) {
element.style.setProperty(
“font-weight”,
“650”,
“important”
);
} else {
element.style.removeProperty(
“font-weight”
);
}
if (
state.uppercase
) {
element.style.setProperty(
“text-transform”,
“uppercase”,
“important”
);
} else {
element.style.removeProperty(
“text-transform”
);
}
if (
state.dyslexia
) {
element.style.setProperty(
“font-family”,
‘”Atkinson Hyperlegible”, Arial, sans-serif’,
“important”
);
}
element.classList.toggle(
“bm-reader-line-enabled”,
state.readingLine
);
element.setAttribute(
“data-bm-reader-theme”,
resolvedTheme
);
if (
resolvedTheme ===
“dark”
) {
element.style.setProperty(
“color”,
“#f5f7fa”,
“important”
);
element.style.setProperty(
“background-color”,
“#17191f”,
“important”
);
} else if (
resolvedTheme ===
“warm”
) {
element.style.setProperty(
“color”,
“#2a2118”,
“important”
);
element.style.setProperty(
“background-color”,
“#fff8e9”,
“important”
);
} else if (
resolvedTheme ===
“gray”
) {
element.style.setProperty(
“color”,
“#1f2328”,
“important”
);
element.style.setProperty(
“background-color”,
“#f1f3f5”,
“important”
);
} else {
element.style.removeProperty(
“color”
);
element.style.removeProperty(
“background-color”
);
}
}
);
if (
state.coloredSyllables
) {
targets.forEach(
applyColoredSyllables
);
} else if (
state.syllables
) {
targets.forEach(
applySyllables
);
}
if (
state.bionic ||
state.font ===
“bionic”
) {
targets.forEach(
applyBionic
);
}
}
/* ==================================================
APPLY SETTINGS
================================================== */
function applySettings() {
updateUI();
applyContentSettings();
saveSettings(
state
);
}
/* ==================================================
ADVANCED BUTTON
================================================== */
if (
advancedToggle &&
advanced &&
advancedLabel
) {
advancedToggle.addEventListener(
“click”,
function () {
var expanded =
advancedToggle.getAttribute(
“aria-expanded”
) ===
“true”;
advancedToggle.setAttribute(
“aria-expanded”,
expanded
? “false”
: “true”
);
advanced.hidden =
expanded;
advancedLabel.textContent =
expanded
? “Mai multe opțiuni”
: “Mai puține opțiuni”;
}
);
}
/* ==================================================
FONT MINUS
================================================== */
if (
fontMinus
) {
fontMinus.addEventListener(
“click”,
function () {
state.fontSize =
Math.max(
MIN_FONT_SIZE,
state.fontSize –
FONT_STEP
);
applySettings();
}
);
}
/* ==================================================
FONT PLUS
================================================== */
if (
fontPlus
) {
fontPlus.addEventListener(
“click”,
function () {
state.fontSize =
Math.min(
MAX_FONT_SIZE,
state.fontSize +
FONT_STEP
);
applySettings();
}
);
}
/* ==================================================
DEFAULT FONT SIZE
================================================== */
if (
fontDefault
) {
fontDefault.addEventListener(
“click”,
function () {
state.fontSize =
DEFAULTS.fontSize;
applySettings();
}
);
}
/* ==================================================
LINE HEIGHT
================================================== */
if (
lineInput
) {
lineInput.addEventListener(
“input”,
function () {
state.lineHeight =
Number(
lineInput.value
);
applySettings();
}
);
}
/* ==================================================
SYLLABLES
================================================== */
if (
syllablesButton
) {
syllablesButton.addEventListener(
“click”,
function () {
state.syllables =
!state.syllables;
applySettings();
}
);
}
/* ==================================================
BIONIC
================================================== */
if (
bionicButton
) {
bionicButton.addEventListener(
“click”,
function () {
state.bionic =
!state.bionic;
applySettings();
}
);
}
/* ==================================================
THEMES
================================================== */
panel.querySelectorAll(
“[data-bm-theme]”
)
.forEach(
function (button) {
button.addEventListener(
“click”,
function () {
state.theme =
button.getAttribute(
“data-bm-theme”
);
applySettings();
}
);
}
);
/* ==================================================
WIDTH
================================================== */
panel.querySelectorAll(
“[data-bm-width]”
)
.forEach(
function (button) {
button.addEventListener(
“click”,
function () {
state.width =
button.getAttribute(
“data-bm-width”
);
applySettings();
}
);
}
);
/* ==================================================
ALIGN
================================================== */
panel.querySelectorAll(
“[data-bm-align]”
)
.forEach(
function (button) {
button.addEventListener(
“click”,
function () {
state.align =
button.getAttribute(
“data-bm-align”
);
applySettings();
}
);
}
);
/* ==================================================
TOOLS
================================================== */
panel.querySelectorAll(
“[data-bm-tool]”
)
.forEach(
function (button) {
button.addEventListener(
“click”,
function () {
var tool =
button.getAttribute(
“data-bm-tool”
);
if (
typeof state[tool] ===
“boolean”
) {
state[tool] =
!state[tool];
if (
tool ===
“dyslexia” &&
state.dyslexia
) {
state.font =
“atkinson”;
}
applySettings();
}
}
);
}
);
/* ==================================================
FONT SELECTOR
================================================== */
panel.querySelectorAll(
“[data-bm-font]”
)
.forEach(
function (button) {
button.addEventListener(
“click”,
function () {
state.font =
button.getAttribute(
“data-bm-font”
);
state.dyslexia =
state.font ===
“atkinson”;
if (
state.font ===
“bionic”
) {
state.bionic =
true;
}
applySettings();
}
);
}
);
/* ==================================================
RESET
================================================== */
if (
resetButton
) {
resetButton.addEventListener(
“click”,
function () {
state =
cloneDefaults();
try {
localStorage.removeItem(
STORAGE_KEY
);
} catch (error) {}
applySettings();
}
);
}
/* ==================================================
READING LINE
================================================== */
document.addEventListener(
“pointermove”,
function (event) {
if (
!state.readingLine ||
!targets.length
) {
return;
}
var target =
event.target.closest
? event.target.closest(
“[data-bm-reading-target]”
)
: null;
if (
!target ||
targets.indexOf(
target
) === -1
) {
return;
}
removeReadingLine();
target.classList.add(
“bm-reader-line-active”
);
}
);
document.addEventListener(
“pointerleave”,
removeReadingLine,
true
);
/* ==================================================
SYSTEM THEME CHANGE
================================================== */
if (
window.matchMedia
) {
var media =
window.matchMedia(
“(prefers-color-scheme: dark)”
);
var systemThemeChanged =
function () {
if (
state.theme ===
“system”
) {
applyContentSettings();
}
};
if (
media.addEventListener
) {
media.addEventListener(
“change”,
systemThemeChanged
);
} else if (
media.addListener
) {
media.addListener(
systemThemeChanged
);
}
}
/* ==================================================
INITIAL APPLY
================================================== */
applySettings();
/*
* Retry pentru page buildere care încarcă textul
* puțin mai târziu.
*/
[
250,
750,
1500,
3000
]
.forEach(
function (delay) {
window.setTimeout(
function () {
if (
!targets.length
) {
applyContentSettings();
}
},
delay
);
}
);
}
/* ======================================================
START ALL READERS
====================================================== */
function startReaders() {
document.querySelectorAll(
“[data-bm-reader]”
)
.forEach(
function (
panel,
index
) {
if (
panel.getAttribute(
“data-bm-initialized”
) ===
“1”
) {
return;
}
panel.setAttribute(
“data-bm-initialized”,
“1”
);
initReader(
panel,
index + 1
);
}
);
}
/* ======================================================
DOM READY
====================================================== */
if (
document.readyState ===
“loading”
) {
document.addEventListener(
“DOMContentLoaded”,
startReaders,
{
once: true
}
);
} else {
startReaders();
}
})();
Melc, melc,codobelc,
Scoate coarne bourești
Și te du la baltă,
De bea apă caldă;
Și te du la Dunăre,
De bea apa tulbure;
Și te suie pe buștean,
De mănâncă leuștean
Și te suie pe uluci,
De mănâncă mere dulci,
Și te suie pe butoi,
De mănâncă usturoi.