Files
claudeandClaude Sonnet 4.6 489bdb01ec
Build and Deploy Verso / deploy (push) Successful in 10m36s
feat(pdf): dark mode for Quarto RevealJS HTML output
Apply the same CSS inversion filter to the HTML iframe as is already
applied to pdfjs PDF pages, so Quarto RevealJS presentations respect
the dark mode toggle. Also show the theme button for HTML outputs and
relax the darkModePdf condition to activate for iframes regardless of
the pdfViewer setting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 17:18:29 +00:00

523 lines
10 KiB
SCSS

:root {
--pdf-bg: var(--bg-dark-secondary);
--pdf-toolbar-btn-hover-color: rgb(125 125 125 / 20%);
--synctex-control-size: 24px;
}
@include theme('light') {
--pdf-toolbar-btn-hover-color: var(--neutral-10);
--pdf-bg: var(--neutral-10);
}
.pdf-code-check-failed-banner-container {
position: absolute;
top: calc(var(--toolbar-small-height) + var(--spacing-04));
left: 0;
right: 0;
z-index: 1;
display: flex;
justify-content: center;
padding: 0 var(--spacing-06);
}
.pdf-code-check-failed-banner {
display: flex;
align-items: center;
gap: var(--spacing-02);
background-color: var(--bg-light-primary);
color: var(--content-primary);
border: 1px solid var(--border-divider);
padding: var(--spacing-02) var(--spacing-03) var(--spacing-02)
var(--spacing-02);
border-radius: var(--border-radius-full);
box-shadow: 0 2px 4px 0 #1e253029;
}
.pdf .toolbar.toolbar-pdf {
@include toolbar-sm-height;
@include toolbar-alt-bg;
padding-right: var(--spacing-03);
margin-left: 0;
.btn.disabled,
.btn[disabled] {
pointer-events: auto;
cursor: not-allowed;
opacity: 1;
}
}
.toolbar-pdf-left {
gap: var(--spacing-02);
.compile-button-group {
height: 24px;
background-color: var(--bg-accent-01);
border-radius: 12px;
margin-left: var(--spacing-02);
.btn-primary:hover {
z-index: auto; // prevents border from being hidden
}
}
.dropdown > .btn {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
&[disabled],
&[disabled]:active {
background-color: var(--bs-btn-bg);
color: var(--bs-btn-color);
opacity: 1;
pointer-events: auto;
cursor: not-allowed;
}
}
.dropdown > .compile-button {
border-top-left-radius: 12px;
border-bottom-left-radius: 12px;
font-size: var(--font-size-02);
}
.dropdown > .compile-dropdown-toggle {
width: 26px;
padding: var(--spacing-01);
}
}
.toolbar-pdf-left,
.toolbar-pdf-right,
.toolbar-pdf-controls {
display: flex;
align-items: center;
align-self: stretch;
}
.toolbar-pdf-controls {
margin-right: var(--spacing-02);
justify-content: flex-end;
flex: 1 1 100%;
}
.toolbar-pdf-right {
flex: 1;
justify-content: flex-end;
}
.toolbar-pdf-orphan {
color: var(--toolbar-btn-color);
}
.toolbar-pdf-orphan-btn,
.toolbar-pdf-orphan {
margin-left: var(--spacing-02);
}
.btn.pdf-toolbar-btn {
display: inline-block;
color: var(--toolbar-btn-color);
background-color: transparent;
padding: 0 var(--spacing-01);
line-height: 1;
height: 24px;
border-radius: var(--border-radius-base);
text-decoration: none;
&:hover,
&:active,
&:focus {
color: var(--toolbar-btn-color);
}
&:hover {
&:not(:disabled) {
background-color: var(--pdf-toolbar-btn-hover-color);
}
}
&:active {
background-color: transparent;
}
.button-content {
align-self: center;
}
.badge {
font-size: 60%;
}
&.log-btn {
border: none;
&.active {
color: var(--white);
background-color: var(--link-color);
box-shadow: none;
opacity: 0.65;
&:hover {
&:not(:disabled) {
background-color: transparent;
color: var(--toolbar-btn-color);
}
}
}
&:focus {
outline: none;
}
}
&.theme-toggle-btn {
border: none;
border-radius: var(--border-radius-full);
&.active {
color: var(--white);
background-color: var(--green-70);
&:hover {
background-color: var(--green-60);
}
}
&:focus {
outline: none;
}
}
}
.pdf {
background-color: var(--pdf-bg);
&.pdf-dark-mode {
--pdf-bg: var(--bg-dark-secondary);
.pdfjs-viewer {
--canvas-box-shadow: none;
.page {
filter: invert(95%) hue-rotate(180deg) brightness(90%) contrast(90%);
}
}
.pdf-viewer iframe {
filter: invert(95%) hue-rotate(180deg) brightness(90%) contrast(90%);
}
}
}
.pdf-viewer,
.pdf-errors,
.pdf-uncompiled {
@extend .full-size;
top: var(--toolbar-small-height);
}
.pdf-viewer {
isolation: isolate;
iframe {
width: 100%;
height: 100%;
border: none;
}
.pdfjs-viewer {
@extend .full-size;
--canvas-box-shadow: 0 0 10px rgb(0 0 0 / 50%);
background-color: transparent;
overflow: scroll;
/* stylelint-disable-next-line selector-class-pattern */
.canvasWrapper > canvas,
div.pdf-canvas {
background: white;
box-shadow: var(--canvas-box-shadow);
}
div.pdf-canvas.pdfng-empty {
background-color: var(--white);
}
div.pdf-canvas.pdfng-loading {
background-color: var(--white);
}
.page-container {
margin: var(--spacing-05) auto;
padding: 0 var(--spacing-05);
box-sizing: content-box;
user-select: none;
}
.page {
display: block; // prevent Overleaf's .loading class (display:inline-flex) from affecting PDF.js page state
box-sizing: content-box;
margin: var(--spacing-05) auto;
box-shadow:
0 5px 5px 0 #23282f0d,
0 3px 14px 0 #23282f08,
0 8px 10px 0 #23282f14;
border: none;
// Overleaf has its own loading state UI; suppress the PDF.js page-level loading
// spinner so the loadingIcon/loading classes have no layout or visual effect.
/* stylelint-disable-next-line selector-class-pattern */
&.loadingIcon::after {
display: none;
}
}
.pdfjs-viewer-inner {
position: absolute;
overflow-y: scroll;
width: 100%;
height: 100%;
-webkit-font-smoothing: initial;
-moz-osx-font-smoothing: initial;
view-transition-name: pdf-viewer;
/* fix review-panel overflow issue, see: https://github.com/overleaf/internal/issues/6781#issuecomment-1112708638 */
/* stylelint-disable-next-line selector-class-pattern */
.pdfViewer {
min-height: 100%;
}
}
&:focus-within {
outline: none;
}
/* Avoid slowdown in Safari when text layers are reset on selection change */
/* stylelint-disable-next-line selector-class-pattern */
.textLayer {
will-change: transform;
}
/* Avoid multiple small layers within annotation layer */
/* stylelint-disable-next-line selector-class-pattern */
.annotationLayer {
will-change: transform;
}
/* Avoids https://github.com/mozilla/pdf.js/issues/13840 in Chrome */
/* stylelint-disable-next-line selector-class-pattern */
.textLayer br::selection {
background: transparent;
}
}
.progress-thin {
position: absolute;
top: -2px;
height: 3px;
left: 0;
right: 0;
.progress-bar {
height: 100%;
background-color: var(--link-color);
}
}
}
::view-transition-old(pdf-viewer),
::view-transition-new(pdf-viewer) {
animation-duration: 0.25s;
}
.pdfjs-viewer-controls {
display: flex;
align-items: center;
justify-content: flex-end;
width: 100%;
}
.pdfjs-zoom-controls {
display: inline-flex;
border-left: 1px solid rgb(125 125 125 / 30%);
}
.pdfjs-toolbar-buttons {
display: flex;
gap: var(--spacing-04);
margin-left: var(--spacing-04);
margin-right: var(--spacing-04);
.btn {
border-radius: var(--border-radius-base) !important;
}
}
.pdfjs-toolbar-button {
padding: var(--spacing-01) !important;
display: flex;
align-items: center;
}
.pdfjs-zoom-dropdown-button {
width: 60px;
text-align: right;
font-weight: normal;
}
.pdfjs-custom-zoom-menu-item {
display: block;
pointer-events: initial !important;
&:hover {
background-color: initial !important;
color: initial !important;
cursor: initial !important;
}
}
.pdfjs-page-number-input {
color: var(--toolbar-btn-color);
font-size: var(--font-size-02);
padding-right: var(--spacing-04);
display: flex;
align-items: center;
gap: var(--spacing-02);
input {
background-color: var(--bg-primary-themed);
color: var(--content-primary-themed);
border: 1px solid var(--border-primary-themed);
width: 32px;
height: 24px;
border-radius: var(--border-radius-base);
text-align: center;
}
}
.pdfjs-viewer-controls-small {
display: flex;
align-items: center;
gap: var(--spacing-04);
}
.pdfjs-toolbar-popover-button {
padding: var(--spacing-01) !important;
}
.pdfjs-toolbar-popover {
background-color: var(--editor-toolbar-bg);
border-radius: var(--border-radius-base);
.popover-arrow {
display: none;
}
button {
background-color: transparent;
color: var(--toolbar-btn-color);
}
.popover-body {
display: flex;
align-items: center;
padding: var(--spacing-04) 0;
}
}
// The new viewer UI has overflow on the inner element,
// so disable the overflow on the outer element
.pdf-viewer .pdfjs-viewer.pdfjs-viewer-outer {
overflow: hidden;
}
:fullscreen {
/* stylelint-disable selector-class-pattern */
.pdfViewer {
min-height: auto !important;
}
.pdfjs-viewer-inner {
overflow-y: hidden !important;
display: flex;
align-items: center;
justify-content: center;
}
}
.synctex-controls {
margin-right: calc(var(--spacing-04) * -1);
position: absolute;
z-index: 6;
padding: 0;
top: 68px;
}
.synctex-control:not(.detach-synctex-control) {
align-items: center;
background-color: rgba($bg-dark-tertiary, 0.8);
border-color: transparent;
border-radius: var(--border-radius-full);
color: var(--content-primary-dark);
display: flex !important;
height: var(--synctex-control-size);
justify-content: center;
margin-bottom: var(--spacing-05);
padding: 0;
transition: background 0.15s ease;
width: var(--synctex-control-size);
&:focus:not(:focus-visible) {
outline: none;
}
&:active {
background-color: rgba($bg-dark-tertiary, 0.8);
color: var(--content-primary-dark);
}
}
.synctex-control {
.synctex-control-icon {
font-size: var(--font-size-06);
font-weight: normal;
}
}
.keyboard-tooltip {
.tooltip-inner {
max-width: none;
}
}
#dropdown-files-logs-pane-list {
overflow-y: auto;
.dropdown-header {
white-space: nowrap;
}
// This keeps the dropdown menu inside the Logs div.
// "spacing-11" is there to compensate the button height and the margin around the logs.
max-height: calc(
100vh - #{$toolbar-small-height + $toolbar-height + $spacing-11}
);
}
.pdf-preview-messages {
position: absolute;
right: var(--spacing-06);
left: var(--spacing-06);
top: var(--spacing-10);
z-index: 1;
}
.synctex-error-toast-content {
display: flex;
gap: 20px;
}