@internetarchive/bookreader 5.0.0-61 → 5.0.0-62

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,11 +2,13 @@
2
2
  * Custom overrides for BookReader Demo.
3
3
  */
4
4
  html {
5
+ /** This must be set because the nav menu uses rem and sets the fonts really big? */
5
6
  font-size: 10px;
6
7
  font-family: sans-serif;
7
8
  }
8
9
 
9
10
  body {
11
+ font-size: 16px;
10
12
  background-color: #939598;
11
13
  margin: 0px;
12
14
  }
@@ -72,6 +72,97 @@
72
72
  }
73
73
  </style>
74
74
  <section class="demos">
75
+ <details class="demo">
76
+ <summary>Test books</summary>
77
+ <ul>
78
+ <li>
79
+ Misc English book
80
+ <ul>
81
+ <li>
82
+ <a href="/BookReaderDemo/demo-internetarchive.html?ocaid=countofmontecris00duma_7">
83
+ <i>The Count of Monte-Cristo</i> by Alexandre Dumas
84
+ </a>
85
+ </li>
86
+ <li>
87
+ <a href="/BookReaderDemo/demo-internetarchive.html?ocaid=driitaleofdaring00bachuoft">
88
+ <i>D’ri and I</i> by Irving Bacheller
89
+ </a>
90
+ </li>
91
+ </ul>
92
+ </li>
93
+ <li>
94
+ Misc non-English book (<a href="https://archive.org/search?query=%21language%3Aeng&sort=-week&and%5B%5D=lending%3A%22is_readable%22&and%5B%5D=mediatype%3A%22texts%22">Search for more</a>)
95
+ <ul>
96
+ <li>
97
+ French: <a href="/BookReaderDemo/demo-internetarchive.html?ocaid=lecomtedemontecr01dumauoft">
98
+ <i>Le Comte de Monte-Cristo</i> by Alexandre Dumas
99
+ </a>
100
+ </li>
101
+ </ul>
102
+ </li>
103
+ <li>
104
+ Right-to-left book (<a href="https://archive.org/search?query=page-progression%3Arl&sort=-week&and%5B%5D=lending%3A%22is_readable%22&and%5B%5D=mediatype%3A%22texts%22">Search for more</a>)
105
+ <ul>
106
+ <li>
107
+ <a href="/BookReaderDemo/demo-internetarchive.html?ocaid=gendaitankashu00meijuoft">
108
+ <i>Gendai tanka shu</i> by Meiji, Emperor of Japan
109
+ </a>
110
+ </li>
111
+ </ul>
112
+ </li>
113
+ <li>
114
+ Newspaper with columns
115
+ <ul>
116
+ <li>
117
+ <a href="/BookReaderDemo/demo-internetarchive.html?ocaid=Crusader-Vol_28_Nos_1-20_Sept_1986-April_1987">
118
+ Crusader-Vol_28_Nos_1-20_Sept_1986-April_1987
119
+ </a>
120
+ </li>
121
+ </ul>
122
+ </li>
123
+ <li>
124
+ Text Selection
125
+ <ul>
126
+ <li>
127
+ OCR with spaces inside each <code>&lt;WORD&gt;</code> element
128
+ <ul>
129
+ <li><a href="/BookReaderDemo/demo-internetarchive.html?ocaid=theworksofplato01platiala">
130
+ <i>The Works of Plato</i> by Plato
131
+ </a></li>
132
+ </ul>
133
+ </li>
134
+ <li>
135
+ Weird font-sizes/indents
136
+ <ul>
137
+ <li><a href="/BookReaderDemo/demo-internetarchive.html?ocaid=cihm_58393#page/n3">
138
+ Microfilm poster thing
139
+ </a></li>
140
+ </ul>
141
+ </li>
142
+ <li>
143
+ Book with short lines
144
+ <ul>
145
+ <li>
146
+ <a href="/BookReaderDemo/demo-internetarchive.html?ocaid=countofmontecris00duma_7#page/261/mode/2up">
147
+ <i>The Count of Monte-Cristo</i>, p.261, second last line
148
+ </a>
149
+ </li>
150
+ </ul>
151
+ </li>
152
+ <li>
153
+ Book with double-spaced lines
154
+ <ul>
155
+ <li>
156
+ <a href="/BookReaderDemo/demo-internetarchive.html?ocaid=calnevpipelineex02unit#page/n93/mode/2up">
157
+ calnevpipelineex02unit
158
+ </a>
159
+ </li>
160
+ </ul>
161
+ </li>
162
+ </ul>
163
+ </li>
164
+ </ul>
165
+ </details>
75
166
  <div class="demo">
76
167
  <button id="toggle-loggedin">Toggle Logged in view</button>
77
168
  <p>Features behind signed in gate: Bookmarks</p>
package/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ # 5.0.0-62
2
+ - Fix: Make text selection work in Safari 15.4+ @cdrini
3
+ - Fix: Rewrite/improvements to text selection UX @cdrini
4
+ - Switches from SVG text layer to HTML text layer
5
+
1
6
  # 5.0.0-61
2
7
  - Fix: Mode2up preview pages hanging on first click @cdrini
3
8
  - Dev: Add analytics event for text layer page selection @cdrini
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/bookreader",
3
- "version": "5.0.0-61",
3
+ "version": "5.0.0-62",
4
4
  "description": "The Internet Archive BookReader.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -46,7 +46,7 @@
46
46
  "@babel/plugin-proposal-class-properties": "7.16.7",
47
47
  "@babel/plugin-proposal-decorators": "7.17.9",
48
48
  "@babel/preset-env": "7.16.11",
49
- "@open-wc/testing-helpers": "^2.2.0",
49
+ "@open-wc/testing-helpers": "^2.2.1",
50
50
  "@types/jest": "^29.5.0",
51
51
  "@webcomponents/webcomponentsjs": "^2.6.0",
52
52
  "babel-loader": "8.2.5",
@@ -45,8 +45,8 @@ export class BookModel {
45
45
  heights.push(page.heightInches);
46
46
  }
47
47
 
48
- widths.sort();
49
- heights.sort();
48
+ widths.sort((a, b) => a - b);
49
+ heights.sort((a, b) => a - b);
50
50
 
51
51
  this._medianPageSize = {
52
52
  width: widths[Math.floor(widths.length / 2)],
@@ -132,7 +132,7 @@ export class ModeThumb {
132
132
  for (let i = 1; i < this.br.thumbRowBuffer; i++) {
133
133
  if (firstRow - i >= 0) { rowsToDisplay.push(firstRow - i); }
134
134
  }
135
- rowsToDisplay.sort();
135
+ rowsToDisplay.sort((a, b) => a - b);
136
136
 
137
137
  // Create the thumbnail divs and images (lazy loaded)
138
138
  for (const row of rowsToDisplay) {
@@ -94,6 +94,18 @@ export function createSVGPageLayer(page, className) {
94
94
  return svg;
95
95
  }
96
96
 
97
+ /**
98
+ * @param {PageModel} page
99
+ * @param {string} className
100
+ */
101
+ export function createDIVPageLayer(page, className) {
102
+ const div = document.createElement("div");
103
+ div.style.width = `${page.width}px`;
104
+ div.style.height = `${page.height}px`;
105
+ div.setAttribute('class', `BRPageLayer ${className}`);
106
+ return div;
107
+ }
108
+
97
109
  /**
98
110
  * @param {{ l: number, r: number, b: number, t: number }} box
99
111
  */
@@ -8,8 +8,6 @@ export const EVENTS = {
8
8
  stop: 'stop',
9
9
  resize: 'resize',
10
10
  userAction: 'userAction', // event to know if user is actively reading
11
- // nav events:
12
- navToggled: 'navToggled',
13
11
  // menu click events
14
12
  fullscreenToggled: 'fullscreenToggled',
15
13
  zoomOut: 'zoomOut',
@@ -0,0 +1,43 @@
1
+ // @ts-check
2
+ export class SelectionObserver {
3
+ selecting = false;
4
+ startedInSelector = false;
5
+ /** @type {HTMLElement} */
6
+ target = null;
7
+
8
+ /**
9
+ * @param {string} selector
10
+ * @param {function('started' | 'cleared', HTMLElement): any} handler
11
+ */
12
+ constructor(selector, handler) {
13
+ this.selector = selector;
14
+ this.handler = handler;
15
+ }
16
+
17
+ attach() {
18
+ // We can't just use selectstart, because safari on iOS just
19
+ // randomly decides when to fire it 😤
20
+ // document.addEventListener("selectstart", this._onSelectStart);
21
+ // This has to be on document :/
22
+ document.addEventListener("selectionchange", this._onSelectionChange);
23
+ }
24
+
25
+ detach() {
26
+ document.removeEventListener("selectionchange", this._onSelectionChange);
27
+ }
28
+
29
+ _onSelectionChange = () => {
30
+ const sel = window.getSelection();
31
+
32
+ if (!this.selecting && sel.toString()) {
33
+ this.selecting = true;
34
+ this.target = $(sel.anchorNode).closest(this.selector)[0];
35
+ this.handler('started', this.target);
36
+ }
37
+
38
+ if (this.selecting && (sel.isCollapsed || !sel.toString() || !$(sel.anchorNode).closest(this.selector)[0])) {
39
+ this.selecting = false;
40
+ this.handler('cleared', this.target);
41
+ }
42
+ };
43
+ }
package/src/BookReader.js CHANGED
@@ -1412,10 +1412,6 @@ BookReader.prototype.bindNavigationHandlers = function() {
1412
1412
  this.trigger(BookReader.eventNames.userAction);
1413
1413
  });
1414
1414
 
1415
- jIcons.filter('.fit').bind('fit', function() {
1416
- // XXXmang implement autofit zoom
1417
- });
1418
-
1419
1415
  for (const control in navigationControls) {
1420
1416
  jIcons.filter(`.${control}`).on('click.bindNavigationHandlers', () => {
1421
1417
  navigationControls[control]();
@@ -1488,268 +1484,8 @@ BookReader.prototype.bindNavigationHandlers = function() {
1488
1484
  self.$('.BRnavCntl').animate({opacity:.75},250);
1489
1485
  }
1490
1486
  });
1491
-
1492
- this.initSwipeData();
1493
-
1494
- $(document).off('mousemove.navigation', this.el);
1495
- $(document).on(
1496
- 'mousemove.navigation',
1497
- this.el,
1498
- { 'br': this },
1499
- this.navigationMousemoveHandler
1500
- );
1501
-
1502
- $(document).off('mousedown.swipe', '.BRpageimage');
1503
- $(document).on(
1504
- 'mousedown.swipe',
1505
- '.BRpageimage',
1506
- { 'br': this },
1507
- this.swipeMousedownHandler
1508
- );
1509
-
1510
- this.bindMozTouchHandlers();
1511
- };
1512
-
1513
- /**
1514
- * Unbind navigation handlers
1515
- */
1516
- BookReader.prototype.unbindNavigationHandlers = function() {
1517
- $(document).off('mousemove.navigation', this.el);
1518
- };
1519
-
1520
- /**
1521
- * Handle mousemove related to navigation. Bind at #BookReader level to allow autohide.
1522
- */
1523
- BookReader.prototype.navigationMousemoveHandler = function(event) {
1524
- // $$$ possibly not great to be calling this for every mousemove
1525
- if (event.data['br'].uiAutoHide) {
1526
- // 77px is an approximate height of the Internet Archive Top Nav
1527
- // 75 & 76 (pixels) provide used in this context is checked against the IA top nav height
1528
- const navkey = $(document).height() - 75;
1529
- if ((event.pageY < 76) || (event.pageY > navkey)) {
1530
- // inside or near navigation elements
1531
- event.data['br'].hideNavigation();
1532
- } else {
1533
- event.data['br'].showNavigation();
1534
- }
1535
- }
1536
- };
1537
-
1538
- BookReader.prototype.initSwipeData = function(clientX, clientY) {
1539
- /*
1540
- * Based on the really quite awesome "Today's Guardian" at http://guardian.gyford.com/
1541
- */
1542
- this._swipe = {
1543
- mightBeSwiping: false,
1544
- didSwipe: false,
1545
- mightBeDraggin: false,
1546
- didDrag: false,
1547
- startTime: (new Date).getTime(),
1548
- startX: clientX,
1549
- startY: clientY,
1550
- lastX: clientX,
1551
- lastY: clientY,
1552
- deltaX: 0,
1553
- deltaY: 0,
1554
- deltaT: 0
1555
- };
1556
- };
1557
-
1558
- BookReader.prototype.swipeMousedownHandler = function(event) {
1559
- const self = event.data['br'];
1560
-
1561
- // We should be the last bubble point for the page images
1562
- // Disable image drag and select, but keep right-click
1563
- if (event.which == 3) {
1564
- return !self.protected;
1565
- }
1566
-
1567
- $(event.target).on('mouseout.swipe',
1568
- { 'br': self},
1569
- self.swipeMouseupHandler
1570
- ).on('mouseup.swipe',
1571
- { 'br': self},
1572
- self.swipeMouseupHandler
1573
- ).on('mousemove.swipe',
1574
- { 'br': self },
1575
- self.swipeMousemoveHandler
1576
- );
1577
-
1578
- self.initSwipeData(event.clientX, event.clientY);
1579
- self._swipe.mightBeSwiping = true;
1580
- self._swipe.mightBeDragging = true;
1581
-
1582
- event.preventDefault();
1583
- event.returnValue = false;
1584
- event.cancelBubble = true;
1585
- return false;
1586
- };
1587
-
1588
- BookReader.prototype.swipeMousemoveHandler = function(event) {
1589
- const self = event.data['br'];
1590
- const _swipe = self._swipe;
1591
- if (! _swipe.mightBeSwiping) {
1592
- return;
1593
- }
1594
-
1595
- // Update swipe data
1596
- _swipe.deltaX = event.clientX - _swipe.startX;
1597
- _swipe.deltaY = event.clientY - _swipe.startY;
1598
- _swipe.deltaT = (new Date).getTime() - _swipe.startTime;
1599
-
1600
- const absX = Math.abs(_swipe.deltaX);
1601
- const absY = Math.abs(_swipe.deltaY);
1602
-
1603
- // Minimum distance in the amount of tim to trigger the swipe
1604
- const minSwipeLength = Math.min(self.refs.$br.width() / 5, 80);
1605
- const maxSwipeTime = 400;
1606
-
1607
- // Check for horizontal swipe
1608
- if (absX > absY && (absX > minSwipeLength) && _swipe.deltaT < maxSwipeTime) {
1609
- _swipe.mightBeSwiping = false; // only trigger once
1610
- _swipe.didSwipe = true;
1611
- if (self.mode == self.constMode2up) {
1612
- if (_swipe.deltaX < 0) {
1613
- self.right();
1614
- } else {
1615
- self.left();
1616
- }
1617
- }
1618
- }
1619
-
1620
- if ( _swipe.deltaT > maxSwipeTime && !_swipe.didSwipe) {
1621
- if (_swipe.mightBeDragging) {
1622
- // Dragging
1623
- _swipe.didDrag = true;
1624
- self.refs.$brContainer
1625
- .scrollTop(self.refs.$brContainer.scrollTop() - event.clientY + _swipe.lastY)
1626
- .scrollLeft(self.refs.$brContainer.scrollLeft() - event.clientX + _swipe.lastX);
1627
- }
1628
- }
1629
- _swipe.lastX = event.clientX;
1630
- _swipe.lastY = event.clientY;
1631
-
1632
- event.preventDefault();
1633
- event.returnValue = false;
1634
- event.cancelBubble = true;
1635
- return false;
1636
- };
1637
-
1638
- BookReader.prototype.swipeMouseupHandler = function(event) {
1639
- const _swipe = event.data['br']._swipe;
1640
- _swipe.mightBeSwiping = false;
1641
- _swipe.mightBeDragging = false;
1642
-
1643
- $(event.target).off('mouseout.swipe').off('mouseup.swipe').off('mousemove.swipe');
1644
-
1645
- if (_swipe.didSwipe || _swipe.didDrag) {
1646
- // Swallow event if completed swipe gesture
1647
- event.preventDefault();
1648
- event.returnValue = false;
1649
- event.cancelBubble = true;
1650
- return false;
1651
- }
1652
- return true;
1653
- };
1654
-
1655
- BookReader.prototype.bindMozTouchHandlers = function() {
1656
- const self = this;
1657
-
1658
- // Currently only want touch handlers in 2up
1659
- this.refs.$br
1660
- .on('MozTouchDown', function(event) {
1661
- if (this.mode == self.constMode2up) {
1662
- event.preventDefault();
1663
- }
1664
- })
1665
- .on('MozTouchMove', function(event) {
1666
- if (this.mode == self.constMode2up) {
1667
- event.preventDefault();
1668
- }
1669
- })
1670
- .on('MozTouchUp', function(event) {
1671
- if (this.mode == self.constMode2up) {
1672
- event.preventDefault();
1673
- }
1674
- });
1675
- };
1676
-
1677
- /**
1678
- * Returns true if the navigation elements are currently visible
1679
- * @return {boolean}
1680
- */
1681
- BookReader.prototype.navigationIsVisible = function() {
1682
- // $$$ doesn't account for transitioning states, nav must be fully visible to return true
1683
- const toolpos = this.refs.$BRtoolbar.position();
1684
- const tooltop = toolpos.top;
1685
- return tooltop == 0;
1686
1487
  };
1687
1488
 
1688
- /**
1689
- * Main controller that sets navigation into view.
1690
- * Defaults to SHOW the navigation chrome
1691
- */
1692
- BookReader.prototype.setNavigationView = function brSetNavigationView(hide) {
1693
- const animationLength = this.constNavAnimationDuration;
1694
- const animationType = 'linear';
1695
- const resizePageContainer = function resizePageContainer () {
1696
- /* main page container fills whole container */
1697
- if (this.constMode2up !== this.mode) {
1698
- const animate = true;
1699
- this.resizeBRcontainer(animate);
1700
- }
1701
- this.trigger(BookReader.eventNames.navToggled);
1702
- }.bind(this);
1703
-
1704
- let toolbarHeight = 0;
1705
- let navbarHeight = 0;
1706
- if (hide) {
1707
- toolbarHeight = this.getToolBarHeight() * -1;
1708
- navbarHeight = this.getFooterHeight() * -1;
1709
-
1710
- this.refs.$BRtoolbar.addClass('js-menu-hide');
1711
- this.refs.$BRfooter.addClass('js-menu-hide');
1712
- } else {
1713
- this.refs.$BRtoolbar.removeClass('js-menu-hide');
1714
- this.refs.$BRfooter.removeClass('js-menu-hide');
1715
- }
1716
-
1717
- this.refs.$BRtoolbar.animate(
1718
- { top: toolbarHeight },
1719
- animationLength,
1720
- animationType,
1721
- resizePageContainer
1722
- );
1723
- this.refs.$BRfooter.animate(
1724
- { bottom: navbarHeight },
1725
- animationLength,
1726
- animationType,
1727
- resizePageContainer
1728
- );
1729
- };
1730
- /**
1731
- * Hide navigation elements, if visible
1732
- */
1733
- BookReader.prototype.hideNavigation = function() {
1734
- // Check if navigation is showing
1735
- if (this.navigationIsVisible()) {
1736
- const hide = true;
1737
- this.setNavigationView(hide);
1738
- }
1739
- };
1740
-
1741
- /**
1742
- * Show navigation elements
1743
- */
1744
- BookReader.prototype.showNavigation = function() {
1745
- // Check if navigation is hidden
1746
- if (!this.navigationIsVisible()) {
1747
- this.setNavigationView();
1748
- }
1749
- };
1750
-
1751
-
1752
-
1753
1489
  /**************************/
1754
1490
  /** BookModel extensions **/
1755
1491
  /**************************/
@@ -43,6 +43,8 @@
43
43
  position: relative;
44
44
  overflow: hidden;
45
45
  background: $brColorPlaceholderBg;
46
+ overflow: hidden;
47
+ overflow: clip;
46
48
  img {
47
49
  position: absolute;
48
50
  background: transparent;
@@ -1,41 +1,70 @@
1
- .textSelectionSVG {
1
+ .BRtextLayer {
2
2
  z-index: 2;
3
-
4
- // Make it so right-clicking on "blank" part of svg sends events to the image (for saving)
3
+ position: absolute;
4
+ top: 0;
5
+ left: 0;
6
+ color: transparent;
7
+ transform-origin: 0 0;
8
+ // Make it so right-clicking on "blank" part of text layer sends events to the image (for saving)
5
9
  pointer-events: none;
6
- .BRwordElement { pointer-events: all; }
7
- &.selectingSVG {
8
- pointer-events: auto;
9
- cursor: text;
10
- }
11
-
12
- // Highlight colors for text selection layer ( these 2 properties do not work if joined)
13
- .BRwordElement::selection {
10
+ cursor: text;
11
+ }
12
+
13
+ .BRparagraphElement {
14
+ margin: 0;
15
+ cursor: text;
16
+ font-family: Georgia, serif;
17
+ line-height: 0;
18
+ }
19
+
20
+ .BRlineElement {
21
+ pointer-events: all;
22
+ white-space: nowrap;
23
+ display: inline-block;
24
+
25
+ // We use display: inline-block, otherwise this causes every line
26
+ // to have newlines at the end in safari. BUT, since they're inline,
27
+ // if the OCR has to short line in the same paragraph, they can wrap
28
+ // and one can go onto the other. This adds an arbitrary margin to
29
+ // the right of each line, forcing things to wrap.
30
+ // See eg https://www-drini.archive.org/details/countofmontecris00duma_7/page/261/mode/2up
31
+ // Page 261, second last line on the page.
32
+ .BRtextLayer[dir=ltr] & { margin-right: 100%; }
33
+ .BRtextLayer[dir=rtl] & { margin-left: 100%; }
34
+ }
35
+
36
+ // Highlight colors for text selection layer
37
+ .BRwordElement, .BRspace, .BRparagraphElement, .BRparagraphElement br {
38
+ // these 2 properties do not work if joined
39
+ &::selection {
14
40
  background: hsla(210, 74%, 62%, 0.4);
15
41
  }
16
- .BRwordElement::-moz-selection {
42
+
43
+ &::-moz-selection {
17
44
  background: hsla(210, 74%, 62%, 0.4);
18
- color: transparent;
45
+ color:transparent;
19
46
  }
47
+ }
20
48
 
21
- .BRparagElement {
22
- fill: transparent;
23
- cursor: text;
24
- white-space: pre;
25
- font-family: Georgia, serif;
26
- }
49
+ .BRparagraphElement br {
50
+ visibility: hidden;
51
+ }
52
+
53
+ // Use CSS pseudo-elements to render the hyphens. This makes them
54
+ // not selectable, so copy/pasting text doesn't include them.
55
+ .BRwordElement--hyphen::after {
56
+ content: "-";
27
57
  }
28
58
 
29
59
  // Hide text layer for performance during zooming & scrolling
30
60
  .BRsmooth-zooming, .BRscrolling-active {
31
- .textSelectionSVG {
61
+ .BRpagecontainer:not(.BRpagecontainer--hasSelection) .BRtextLayer {
32
62
  display: none;
33
63
  }
34
64
  }
35
65
 
36
- // Hide text selection layers of off-screen pages, and only display
37
- // 2 text layers regardless of zoom level
38
- .BRmode1up .BRpagecontainer:not(.BRpage-visible) .textSelectionSVG {
66
+ // Hide text selection layers of off-screen pages
67
+ .BRmode1up .BRpagecontainer:not(.BRpage-visible) .BRtextLayer {
39
68
  display: none;
40
69
  }
41
70
 
@@ -45,3 +74,35 @@
45
74
  -moz-user-select: none;
46
75
  user-select: none;
47
76
  }
77
+
78
+
79
+ // These are Microsoft Edge specific fixed to make some of the
80
+ // browsers features work well. These are for the in-place
81
+ // translation.
82
+ .BRwordElement, .BRspace {
83
+ &[_istranslated="1"], &[_msttexthash] {
84
+ background-color: #e4dccd;
85
+ color: black;
86
+ letter-spacing: unset !important;
87
+ background: #ccbfa7;
88
+ }
89
+ }
90
+
91
+ .BRlineElement font[_mstmutation="1"] {
92
+ background: #ccbfa7;
93
+ }
94
+
95
+ .BRlineElement:has([_istranslated="1"], [_msttexthash]) {
96
+ background-color: #e4dccd;
97
+ color: black;
98
+ text-align: justify;
99
+ width: inherit;
100
+ &:not(:nth-last-child(2)) {
101
+ text-align-last: justify;
102
+ }
103
+ }
104
+
105
+ .BRlineElement[_msttexthash] {
106
+ background: #ccbfa7;
107
+ word-spacing: unset !important;
108
+ }