diff --git a/LICENSES/OFL-1.1.txt b/LICENSES/OFL-1.1.txt new file mode 100644 index 00000000..6fe84ee2 --- /dev/null +++ b/LICENSES/OFL-1.1.txt @@ -0,0 +1,43 @@ +SIL OPEN FONT LICENSE + +Version 1.1 - 26 February 2007 + +PREAMBLE + +The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. + +DEFINITIONS + +"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the copyright statement(s). + +"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, or substituting — in part or in whole — any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS + +Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. + +5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. + +TERMINATION + +This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/REUSE.toml b/REUSE.toml index 10a04318..c94ba1eb 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -8,8 +8,11 @@ path = [ "README.md", "src/**/*.ui", "src/app/resources/browser/*.html", - "src/app/resources/browser/assets/css/highlight.css", - "src/app/resources/browser/assets/css/welcome.css", + "src/app/resources/browser/styles/highlight.css", + "src/app/resources/browser/styles/manual.css", + "src/app/resources/browser/styles/pages.css", + "src/app/resources/browser/styles/prose.css", + "src/app/resources/browser/styles/tokens.css", "src/app/resources/icons/**/*", "src/app/zeal.qrc", ] @@ -32,6 +35,7 @@ path = [ ".clang-format", ".clang-tidy", ".clangd", + "biome.json", ".editorconfig", ".gitattributes", ".github/**/*", @@ -43,11 +47,6 @@ path = [ SPDX-FileCopyrightText = "Oleg Shparber, et al. " SPDX-License-Identifier = "MIT" -[[annotations]] -path = "src/app/resources/browser/assets/css/oat.min.css" -SPDX-FileCopyrightText = "Copyright (c) Kailash Nadh " -SPDX-License-Identifier = "MIT" - [[annotations]] path = "src/contrib/cpp-httplib/httplib.h" SPDX-FileCopyrightText = "Copyright (c) 2023 Yuji Hirose. All rights reserved." @@ -75,3 +74,19 @@ SPDX-License-Identifier = "CC-BY-4.0" path = "assets/tabler/*.svg" SPDX-FileCopyrightText = "Copyright (c) Paweł Kuna " SPDX-License-Identifier = "MIT" + +# OFL-1.1 for the bundled web fonts. +[[annotations]] +path = "src/app/resources/browser/fonts/Inter*.woff2" +SPDX-FileCopyrightText = "Copyright 2016 The Inter Project Authors" +SPDX-License-Identifier = "OFL-1.1" + +[[annotations]] +path = "src/app/resources/browser/fonts/SourceSerif4*.woff2" +SPDX-FileCopyrightText = "Copyright 2014-2023 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'" +SPDX-License-Identifier = "OFL-1.1" + +[[annotations]] +path = "src/app/resources/browser/fonts/GoogleSansCode*.woff2" +SPDX-FileCopyrightText = "Copyright 2025 The Google Sans Code Project Authors (https://github.com/googlefonts/googlesans-code)" +SPDX-License-Identifier = "OFL-1.1" diff --git a/biome.json b/biome.json new file mode 100644 index 00000000..96a77b4a --- /dev/null +++ b/biome.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.5.1/schema.json", + "formatter": { + "enabled": true, + "useEditorconfig": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "complexity": { + "noImportantStyles": "off" + }, + "style": { + "noDescendingSpecificity": "off" + } + } + }, + "files": { + "includes": ["src/app/resources/browser/styles/**"] + } +} diff --git a/src/app/resources/browser/404.html b/src/app/resources/browser/404.html deleted file mode 100644 index 6fda2e34..00000000 --- a/src/app/resources/browser/404.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - Page not found - - - - - -
-
-

Page not found

-

Encountered a problem? Please report it.

-
- -
-

Troubleshoot

-
-

Docset Library

-

Update or reinstall docsets

-
-

Get in touch

-
-

Discord

-

Chat with the developers and other users

-
-
-

GitHub

-

Contribute to the project

-
-
-
- - - - - - - - diff --git a/src/app/resources/browser/assets/css/highlight.css b/src/app/resources/browser/assets/css/highlight.css deleted file mode 100644 index 723cba77..00000000 --- a/src/app/resources/browser/assets/css/highlight.css +++ /dev/null @@ -1,10 +0,0 @@ -/* Highlight on navigation to an anchor. */ -@keyframes targetNavigatedAnimation { - from { background: #fff; } - 50% { background: #ff0; } - to { background: #fff; } -} - -*:target { - animation: targetNavigatedAnimation .5s linear; -} diff --git a/src/app/resources/browser/assets/css/oat.min.css b/src/app/resources/browser/assets/css/oat.min.css deleted file mode 100644 index 5d75d077..00000000 --- a/src/app/resources/browser/assets/css/oat.min.css +++ /dev/null @@ -1 +0,0 @@ -@layer theme,base,components,animations,utilities;@layer base{*,*:before,*:after{box-sizing:border-box;-webkit-tap-highlight-color:transparent}*{margin:0}html{tab-size:4}body,dialog,[popover]{font-family:var(--font-sans);font-size:var(--text-regular);line-height:var(--leading-normal);color:var(--foreground)}body{background-color:var(--background);color:var(--foreground);-webkit-font-smoothing:antialiased}img,picture,video,canvas,svg{max-width:100%}p,h1,h2,h3,h4,h5,h6{overflow-wrap:break-word}h1,h2,h3,h4,h5,h6{font-weight:var(--font-semibold);line-height:1.25;&:first-child{margin-block-start:0}}h1{font-size:var(--text-1);margin:var(--space-10) 0 var(--space-6)}h2{font-size:var(--text-2);margin:var(--space-8) 0 var(--space-5)}h3{font-size:var(--text-3);margin:var(--space-6) 0 var(--space-4)}h4{font-size:var(--text-4);margin:var(--space-5) 0 var(--space-3)}h5{font-size:var(--text-5);margin:var(--space-4) 0 var(--space-2)}h6{font-size:var(--text-regular);margin:var(--space-4) 0 var(--space-2)}p{margin-block-end:var(--space-4);&:last-child{margin-block-end:0}}a{color:var(--primary);text-decoration:underline;text-underline-offset:2px;transition:color var(--transition-fast);&:hover{color:rgb(from var(--primary) r g b / .8)}}strong,b{font-weight:var(--font-semibold)}em,i{font-style:italic}small{font-size:var(--text-7)}code{font-family:var(--font-mono);font-size:.875em;padding:calc(var(--space-1) / 2) var(--space-1);background-color:var(--faint);border-radius:var(--radius-small)}pre{font-family:var(--font-mono);padding:var(--space-4);background-color:var(--faint);border-radius:var(--radius-medium);overflow-x:auto;margin-block-end:var(--space-4);code{padding:0;background:none;border-radius:0}}blockquote{border-inline-start:4px solid var(--border);padding-inline-start:var(--space-4);margin:var(--space-4) 0;color:var(--muted-foreground);font-style:italic}hr{border:none;border-top:1px solid var(--border);margin:var(--space-2) 0}ul,ol{padding-inline-start:var(--space-6);margin-block-end:var(--space-4)}ul{list-style-type:disc}ol{list-style-type:decimal}li{margin-block-end:var(--space-1)}mark{background-color:rgb(from var(--warning) r g b / .3);padding:calc(var(--space-1) / 2) var(--space-1);border-radius:var(--radius-small)}[hidden]{display:none}:focus-visible{outline:2px solid var(--ring);outline-offset:2px}:disabled{opacity:.5;cursor:not-allowed}}@layer theme{:root{color-scheme:light dark;--background: light-dark(#fff, #09090b);--foreground: light-dark(#09090b, #fafafa);--card: light-dark(#fff, #18181b);--card-foreground: light-dark(#09090b, #fafafa);--primary: light-dark(#574747, #fafafa);--primary-foreground: light-dark(#fafafa, #18181b);--secondary: light-dark(#f4f4f5, #27272a);--secondary-foreground: light-dark(#574747, #fafafa);--muted: light-dark(#f4f4f5, #27272a);--muted-foreground: light-dark(#71717a, #a1a1aa);--faint: light-dark(#fafafa, #1e1e21);--faint-foreground: light-dark(#a1a1aa, #71717a);--accent: light-dark(#f4f4f5, #27272a);--danger: light-dark(#d32f2f, #f4807b);--danger-foreground: light-dark(#fafafa, #18181b);--success: light-dark(#008032, #6cc070);--success-foreground: light-dark(#fafafa, #18181b);--warning: light-dark(#a65b00, #f0a030);--warning-foreground: #09090b;--border: light-dark(#d4d4d8, #52525b);--input: light-dark(#d4d4d8, #52525b);--ring: light-dark(#574747, #d4d4d8);--space-1: .25rem;--space-2: .5rem;--space-3: .75rem;--space-4: 1rem;--space-5: 1.25rem;--space-6: 1.5rem;--space-8: 2rem;--space-10: 2.5rem;--space-12: 3rem;--space-14: 3.5rem;--space-16: 4rem;--space-18: 4.5rem;--radius-small: .125rem;--radius-medium: .375rem;--radius-large: .75rem;--radius-full: 9999px;--bar-height: .5rem;--font-sans: system-ui, sans-serif;--font-mono: ui-monospace, Consolas, monospace;--text-1: clamp(1.75rem, 1.5rem + 1.1vw, 2.25rem);--text-2: clamp(1.5rem, 1.3rem + .8vw, 1.875rem);--text-3: clamp(1.25rem, 1.1rem + .5vw, 1.5rem);--text-4: clamp(1.125rem, 1.05rem + .3vw, 1.25rem);--text-5: 1.125rem;--text-6: 1rem;--text-7: .875rem;--text-8: .75rem;--text-regular: var(--text-6);--leading-normal: 1.5;--font-normal: 400;--font-medium: 500;--font-semibold: 600;--font-bold: 600;--shadow-small: 0 1px 2px 0 rgb(0 0 0 / .05);--shadow-medium: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--shadow-large: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--transition-fast: .12s cubic-bezier(.4, 0, .2, 1);--transition: .2s cubic-bezier(.4, 0, .2, 1);--z-dropdown: 50;--z-modal: 200}}@layer animations{.animate-pop-in{opacity:1;transform:perspective(1000px) rotateX(0) translateZ(0);transition:opacity .15s cubic-bezier(.4,0,.2,1),transform .15s cubic-bezier(.4,0,.2,1),overlay .15s cubic-bezier(.4,0,.2,1) allow-discrete,display .15s cubic-bezier(.4,0,.2,1) allow-discrete;@starting-style{opacity:0;transform:perspective(1000px) rotateX(-15deg) translateZ(-80px)}&[data-state=closing]{opacity:0;transform:perspective(1000px) rotateX(-15deg) translateZ(-80px)}&[data-state=closing]::backdrop{opacity:0}}dialog::backdrop{opacity:1;transition:opacity .15s cubic-bezier(.4,0,.2,1);@starting-style{opacity:0}}.animate-slide-in{opacity:1;transform:translate(0);transition:opacity .15s cubic-bezier(.16,1,.3,1),transform .15s cubic-bezier(.16,1,.3,1);@starting-style{opacity:0;transform:translate(100%)}&[data-state=closing]{opacity:0;transform:translate(100%)}}}@layer base{:is(button,[type=submit],[type=reset],[type=button],a.button),::file-selector-button{--_hov: color-mix(in srgb, var(--primary), white 25%);display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);padding:var(--space-2) var(--space-4);font-size:var(--text-7);font-weight:var(--font-medium);line-height:var(--leading-normal);white-space:nowrap;text-decoration:none;background-color:var(--primary);color:var(--primary-foreground);border-radius:var(--radius-medium);border:1px solid;border-color:rgb(from #fff r g b / .15) rgb(from #000 r g b / .2) rgb(from #000 r g b / .2) rgb(from #fff r g b / .15);transition:background-color var(--transition-fast),opacity var(--transition-fast),transform var(--transition-fast);&:not(:disabled){cursor:pointer}&:hover:not(:disabled){background-color:var(--_hov)}&:active:not(:disabled){transform:translate(1px,1px)}&[data-variant=secondary]{--_hov: color-mix(in srgb, var(--secondary), black 10%);background-color:var(--secondary);color:var(--secondary-foreground);border-color:rgb(from #fff r g b / .5) rgb(from #000 r g b / .1) rgb(from #000 r g b / .1) rgb(from #fff r g b / .5)}&[data-variant=danger]{--_hov: color-mix(in srgb, var(--danger), black 15%);background-color:var(--danger);color:var(--danger-foreground)}&:is(.outline,.ghost){--_hov: var(--accent);background-color:transparent;color:var(--foreground);&[data-variant=danger]{--_hov: color-mix(in srgb, var(--danger), transparent 90%);color:var(--danger)}&[data-variant=secondary]{--_hov: color-mix(in srgb, var(--secondary), transparent 80%);color:var(--secondary-foreground)}}&.outline{border-color:var(--border);&[data-variant=danger]{border-color:var(--danger)}&[data-variant=secondary]{border-color:var(--secondary)}}&.ghost{border-color:transparent}&.small{padding:var(--space-1) var(--space-3);font-size:var(--text-8)}&.large{height:3rem;padding:0 var(--space-6);font-size:var(--text-regular)}&.icon{width:2.5rem;padding:0;&.small{width:2rem}&.large{width:3rem}}}::file-selector-button{background-color:transparent;color:var(--foreground);border:1px solid var(--border)}::file-selector-button:hover{background-color:var(--accent)}}@layer components{menu.buttons{list-style-type:none;padding-inline-start:0;display:inline-flex;>li{&:first-child>*{border-start-start-radius:var(--radius-medium);border-end-start-radius:var(--radius-medium)}&:last-child>*{border-start-end-radius:var(--radius-medium);border-end-end-radius:var(--radius-medium)}>*{border-radius:0}&:not(:last-child)>*{border-inline-end:1px solid rgb(from var(--primary-foreground) r g b / .2)}}}}@layer base{label{display:block;font-size:var(--text-7);font-weight:var(--font-medium);&:has(input:where([type=checkbox],[type=radio])){display:inline-flex;align-items:center;gap:var(--space-2);font-weight:var(--font-normal)}}:where(input:not([type=checkbox],[type=radio],[type=range],[type=file],[type=color]),textarea,select){width:100%;margin-block-start:var(--space-1);padding:var(--space-2) var(--space-3);font-size:var(--text-7);line-height:var(--leading-normal);background-color:var(--background);color:var(--foreground);border:1px solid var(--input);border-radius:var(--radius-medium);transition:border-color var(--transition-fast),box-shadow var(--transition-fast);&::placeholder{color:var(--muted-foreground)}&:focus{outline:none;border-color:var(--ring);box-shadow:0 0 0 2px rgb(from var(--ring) r g b / .2);z-index:1}&:disabled{background-color:var(--muted)}&:is([aria-invalid=true],:user-invalid){border-color:var(--danger);&:focus{box-shadow:0 0 0 2px rgb(from var(--danger) r g b / .2)}}}textarea{height:auto;min-height:5rem;padding:var(--space-3);resize:vertical}select{appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2371717a' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right var(--space-2) center;padding-inline-end:var(--space-6)}input:where([type=checkbox],[type=radio]){appearance:none;width:1rem;height:1rem;margin:0;position:relative;background-color:var(--background);border:1px solid var(--input);transition:background-color var(--transition-fast),border-color var(--transition-fast);&:checked{background-color:var(--primary);border-color:var(--primary);&:after{content:"";position:absolute;inset:0;background-color:var(--primary-foreground);mask-position:center;mask-repeat:no-repeat;mask-size:100%}}}input[type=checkbox]{border-radius:var(--radius-small);&:checked:after{mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='4'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E")}&[role=switch]{--switch-height: calc(var(--bar-height) * 3);--switch-inset: 2px;--switch-thumb: calc(var(--switch-height) - var(--switch-inset) * 3);width:calc(var(--switch-height) * 2);height:var(--switch-height);border-radius:var(--radius-full);background-color:var(--input);&:before{content:"";position:absolute;top:50%;left:var(--switch-inset);transform:translateY(-50%);width:var(--switch-thumb);height:var(--switch-thumb);background-color:var(--background);border-radius:var(--radius-full);transition:transform var(--transition);box-shadow:var(--shadow-small)}&:checked{background-color:var(--primary);&:after{content:none}&:before{transform:translateY(-50%) translate(var(--switch-height))}}}}input[type=radio]{border-radius:var(--radius-full);&:checked:after{mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='4' fill='currentColor'/%3E%3C/svg%3E")}}:where(input:where([type=checkbox],[type=radio],[type=range]),select):not(:disabled),label:has(input:where([type=checkbox],[type=radio]):not(:disabled)){cursor:pointer}input[type=range]{width:100%;height:var(--bar-height);appearance:none;background:var(--muted);border-radius:var(--radius-full);&::-webkit-slider-thumb{appearance:none;width:1.25rem;height:1.25rem;background:var(--primary);border-radius:var(--radius-full);transition:transform var(--transition-fast);&:hover{transform:scale(1.1)}}&::-moz-range-thumb{width:1.25rem;height:1.25rem;background:var(--primary);border:none;border-radius:var(--radius-full)}}fieldset{border:1px solid var(--border);border-radius:var(--radius-medium);padding:var(--space-4);margin-block-end:var(--space-4)}legend{font-size:var(--text-7);font-weight:var(--font-medium);padding:0 var(--space-2)}}@layer components{fieldset.group{display:flex;align-items:stretch;border:none;padding:0;margin:0;>:is(input,textarea,select){flex:1;margin-block-start:0;&:not(:focus){border-inline-end-color:transparent}}>:is(input,textarea,select,button){border-radius:0;&:first-child{border-radius:var(--radius-medium) 0 0 var(--radius-medium)}&:last-child{border-radius:0 var(--radius-medium) var(--radius-medium) 0}}>legend{float:inline-start;display:inline-flex;align-items:center;padding:0 var(--space-3);line-height:var(--leading-normal);font-weight:var(--font-normal);color:var(--muted-foreground);background-color:var(--muted);border:1px solid var(--input);border-inline-end:none;border-radius:var(--radius-medium) 0 0 var(--radius-medium)}}[data-field]{margin-block-end:var(--space-4);[data-hint],.error{font-size:var(--text-8);font-weight:var(--font-normal);color:var(--muted-foreground);margin-block-start:var(--space-1)}.error{display:none}&[data-field=error] .error{display:block;color:var(--danger)}}}@layer base{table{border-collapse:collapse;table-layout:fixed;width:100%;font-size:var(--text-7)}thead{border-bottom:1px solid var(--border)}th,td{overflow-wrap:break-word}th{padding:var(--space-3) var(--space-2);text-align:start;font-weight:var(--font-medium);color:var(--muted-foreground)}td{padding:var(--space-3) var(--space-2)}tbody tr{border-bottom:1px solid var(--border);transition:background-color var(--transition-fast);&:last-child{border-bottom:none}&:hover{background-color:rgb(from var(--muted) r g b / .5)}}}@layer base{progress{appearance:none;width:100%;height:var(--bar-height);border:none;border-radius:var(--radius-full);overflow:hidden;background-color:var(--muted);&::-webkit-progress-bar{background-color:var(--muted);border-radius:var(--radius-full)}&::-webkit-progress-value{background-color:var(--primary);border-radius:var(--radius-full);transition:width var(--transition)}&::-moz-progress-bar{background-color:var(--primary);border-radius:var(--radius-full)}}meter{appearance:none;width:100%;height:var(--bar-height);border:none;border-radius:var(--radius-full);overflow:hidden;background:var(--muted);&::-webkit-meter-bar{background:var(--muted);border:none;border-radius:var(--radius-full);height:var(--bar-height)}&::-webkit-meter-optimum-value,&::-webkit-meter-suboptimum-value,&::-webkit-meter-even-less-good-value{border-radius:var(--radius-full)}&::-webkit-meter-optimum-value{background:var(--success)}&::-webkit-meter-suboptimum-value{background:var(--warning)}&::-webkit-meter-even-less-good-value{background:var(--danger)}&::-moz-meter-bar{background:var(--success);border-radius:var(--radius-full)}&:-moz-meter-sub-optimum::-moz-meter-bar{background:var(--warning)}&:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:var(--danger)}}}@layer components{[aria-busy=true]{&:before{content:"";display:inline-block;inset:0;margin:auto;width:1.5rem;height:1.5rem;border:2px solid var(--muted);border-top-color:var(--primary);border-radius:var(--radius-full);animation:spin 1s linear infinite;text-align:center}&[data-spinner~=small]:before{width:1rem;height:1rem}&[data-spinner~=large]:before{width:2rem;height:2rem;border-width:3px}&[data-spinner~=overlay]{position:relative;>*{opacity:.3;pointer-events:none}&:before{position:absolute;inset:0;margin:auto;z-index:1}}}@keyframes spin{to{transform:rotate(360deg)}}}@layer components{:root{--grid-cols: 12;--grid-gap: 1.5rem;--container-max: 1280px;--container-pad: 1rem}.container{width:100%;max-width:var(--container-max);margin-inline:auto;padding-inline:var(--container-pad)}.row{display:grid;grid-template-columns:repeat(var(--grid-cols),1fr);gap:var(--grid-gap);width:100%}.col,[class*=col-]{grid-column-end:span var(--span, var(--grid-cols))}.col-1{--span: 1}.col-2{--span: 2}.col-3{--span: 3}.col-4{--span: 4}.col-5{--span: 5}.col-6{--span: 6}.col-7{--span: 7}.col-8{--span: 8}.col-9{--span: 9}.col-10{--span: 10}.col-11{--span: 11}.col-12{--span: 12}.offset-1{grid-column-start:2}.offset-2{grid-column-start:3}.offset-3{grid-column-start:4}.offset-4{grid-column-start:5}.offset-5{grid-column-start:6}.offset-6{grid-column-start:7}.col-end{grid-column-start:span var(--span, 1);grid-column-end:-1}@media(max-width:768px){.row{--grid-cols: 4;--grid-gap: 1rem}.col,[class*=col-]{--span: 4}[class*=offset-]{grid-column-start:auto}}}@layer components{.card{background-color:var(--card);color:var(--card-foreground);border:1px solid var(--border);border-radius:var(--radius-medium);box-shadow:var(--shadow-small);padding:var(--space-6);overflow:hidden}}@layer components{[role=alert]{position:relative;display:flex;gap:var(--space-3);padding:var(--space-4) var(--space-6);background-color:var(--background);border:1px solid var(--border);border-radius:var(--radius-medium);font-size:var(--text-7);&[data-variant]{border:none}&[data-variant=error],&[data-variant=danger]{color:var(--danger);background-color:light-dark(color-mix(in srgb,var(--danger) 8%,transparent),color-mix(in srgb,var(--danger) 20%,transparent));& a{color:var(--danger)}}&[data-variant=success]{color:var(--success);background-color:light-dark(color-mix(in srgb,var(--success) 8%,transparent),color-mix(in srgb,var(--success) 20%,transparent));& a{color:var(--success)}}&[data-variant=warning]{color:var(--warning);background-color:light-dark(color-mix(in srgb,var(--warning) 8%,transparent),color-mix(in srgb,var(--warning) 20%,transparent));& a{color:var(--warning)}}}}@layer components{.badge{display:inline-flex;align-items:center;gap:var(--space-1);padding:var(--space-1) var(--space-4);font-size:var(--text-8);font-weight:var(--font-medium);line-height:var(--leading-normal);background-color:var(--primary);color:var(--primary-foreground);border-radius:var(--radius-full);&.secondary{background-color:var(--secondary);color:var(--secondary-foreground)}&.outline{background-color:transparent;color:var(--foreground);border:1px solid var(--border)}&.success{color:var(--success);background-color:light-dark(color-mix(in srgb,var(--success) 10%,transparent),color-mix(in srgb,var(--success) 30%,transparent))}&.warning{color:var(--warning);background-color:light-dark(color-mix(in srgb,var(--warning) 10%,transparent),color-mix(in srgb,var(--warning) 30%,transparent))}&.danger{color:var(--danger);background-color:light-dark(color-mix(in srgb,var(--danger) 10%,transparent),color-mix(in srgb,var(--danger) 30%,transparent))}}}@layer components{details{border:1px solid var(--border);border-radius:var(--radius-medium);overflow:hidden;+details{margin-top:-1px;border-start-start-radius:0;border-start-end-radius:0}&:has(+details){border-end-start-radius:0;border-end-end-radius:0}&[open] summary{border-bottom:1px solid var(--border)}}summary{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2);padding:var(--space-4);font-weight:var(--font-medium);cursor:pointer;user-select:none;transition:background-color var(--transition-fast);&:hover{background-color:var(--muted)}&::-webkit-details-marker,&::marker{display:none}&:after{content:"";width:1em;height:1em;flex-shrink:0;background-color:currentColor;mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");mask-size:contain;mask-repeat:no-repeat;transition:transform var(--transition-fast)}details[open] &:after{transform:rotate(180deg)}}details>*:not(summary){padding:var(--space-4)}}@layer components{[role=tablist]{display:inline-flex;align-items:center;gap:var(--space-1);padding:var(--space-1);background-color:var(--muted);border-radius:var(--radius-medium)}[role=tab]{display:inline-flex;align-items:center;justify-content:center;padding:var(--space-2) var(--space-3);font-size:var(--text-7);font-weight:var(--font-medium);white-space:nowrap;background-color:transparent;color:var(--foreground);border:none;border-radius:calc(var(--radius-medium) - 2px);cursor:pointer;transition:background-color var(--transition-fast),color var(--transition-fast);&:hover{color:var(--muted-foreground)}&[aria-selected=true]{background-color:var(--background);box-shadow:var(--shadow-small)}}[role=tabpanel]{padding:var(--space-4) 0;&:focus-visible{outline:none}}}@layer components{dialog{position:fixed;inset:0;z-index:var(--z-modal);width:min(100% - 2rem,32rem);max-height:85vh;margin:auto;padding:0;background-color:var(--card);border:1px solid var(--border);border-radius:var(--radius-large);box-shadow:var(--shadow-large);overflow:hidden;opacity:0;transform:scale(.95);transition:opacity .15s ease,transform .15s ease,overlay .15s ease allow-discrete,display .15s ease allow-discrete;&[open]{opacity:1;transform:scale(1)}@starting-style{&[open]{opacity:0;transform:scale(.95)}}&::backdrop{background-color:#0000;transition:background-color .15s ease,overlay .15s ease allow-discrete,display .15s ease allow-discrete}&[open]::backdrop{background-color:#00000080}@starting-style{&[open]::backdrop{background-color:#0000}}>header,>form>header{display:flex;flex-direction:column;gap:var(--space-1);padding:var(--space-6);padding-block-end:0;>h1,>h2,>h3,>h4,>h5,>h6{margin-block-end:0}>p{font-size:var(--text-7);color:var(--muted-foreground);margin-block-end:0}}>p,>div,>section,>form>p,>form>div,>form>section{padding:var(--space-6);overflow-y:auto}>footer,>form>footer{display:flex;justify-content:flex-end;gap:var(--space-2);padding:var(--space-6);padding-block-start:0}}}@layer components{ot-dropdown{[popover]{position:fixed;margin:0;min-width:12rem;background-color:var(--background);border:1px solid var(--border);border-radius:var(--radius-medium);box-shadow:var(--shadow-medium);opacity:0;transform:translateY(-4px);transition:opacity .15s ease-out,transform .15s ease-out,display .15s allow-discrete,overlay .15s allow-discrete;&:popover-open{opacity:1;transform:translateY(0)}@starting-style{&:popover-open{opacity:0;transform:translateY(-4px)}}}[role=menuitem]{display:flex;align-items:center;justify-content:start;gap:var(--space-2);width:100%;padding:var(--space-2) var(--space-3);font-size:var(--text-7);text-align:start;color:var(--foreground);background:none;border:none;border-radius:var(--radius-small);cursor:pointer;&:hover,&:focus{background-color:var(--accent);outline:none}}}}@layer components{.toast-container{position:fixed;display:flex;flex-direction:column;pointer-events:none;margin:0;padding:0;border:none;background:transparent;overflow:visible;&::backdrop{display:none}&[data-placement=top-left]{inset:var(--space-4) auto auto var(--space-4)}&[data-placement=top-center]{inset:var(--space-4) auto auto 50%;transform:translate(-50%)}&[data-placement=top-right]{inset:var(--space-4) var(--space-4) auto auto}&[data-placement=bottom-left]{inset:auto auto var(--space-4) var(--space-4);flex-direction:column-reverse}&[data-placement=bottom-center]{inset:auto auto var(--space-4) 50%;transform:translate(-50%);flex-direction:column-reverse}&[data-placement=bottom-right]{inset:auto var(--space-4) var(--space-4) auto;flex-direction:column-reverse}}.toast{--transition: .3s;--transition-in: calc(var(--transition) - 50ms);padding:var(--space-5) var(--space-4);max-width:28rem;min-width:20rem;pointer-events:auto;background-color:var(--card);border:1px solid var(--border);border-inline-start-width:var(--space-1);border-inline-start-style:solid;border-radius:var(--radius-medium);box-shadow:var(--shadow-small);transition:opacity var(--transition-in),transform var(--transition-in),margin var(--transition-in);line-height:1;.toast-title{font-weight:600;margin:0 0 var(--space-3) 0}.toast-message{color:var(--muted-foreground)}&[data-variant=success]{border-inline-start-color:var(--success);.toast-title{color:var(--success)}}&[data-variant=danger]{border-inline-start-color:var(--danger);.toast-title{color:var(--danger)}}&[data-variant=warning]{border-inline-start-color:var(--warning);.toast-title{color:var(--warning)}}>[data-close]{margin-inline-start:auto;background:none;border:none;padding:0;cursor:pointer;opacity:.5;&:hover{opacity:1}}margin:var(--space-2) 0;&[data-entering]{opacity:0;transform:translateY(-1rem)}&[data-exiting]{opacity:0;margin:0;padding-block:0;max-height:0;overflow:hidden;transition:opacity var(--transition),margin var(--transition),padding var(--transition),max-height var(--transition)}}}@layer components{:root{--_nh: var(--space-12)}[data-sidebar-layout]{--topnav-offset: 0px;--topnav-scroll-offset: 0px;display:grid;grid-template-columns:14rem 1fr;min-height:100dvh;gap:var(--space-4);&:has(nav[data-topnav]){--topnav-offset: var(--_nh);--topnav-scroll-offset: calc(var(--_nh) + var(--space-6))}>main{min-width:0;margin-block-start:var(--topnav-offset);[id]{scroll-margin-block-start:var(--topnav-scroll-offset)}}>aside[data-sidebar]{position:sticky;top:var(--topnav-offset);z-index:1;height:calc(100dvh - var(--topnav-offset));align-self:start;background-color:var(--background);border-inline-end:1px solid var(--border);display:flex;flex-direction:column;>:is(header,footer){flex-shrink:0;padding:var(--space-3)}>footer{margin-block-start:auto}>nav{flex:1;min-height:0;overflow-y:auto;padding:var(--space-3) var(--space-2);ul{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:var(--space-1);li{margin:0}}a{display:flex;gap:var(--space-2);padding:var(--space-1) var(--space-3);font-size:var(--text-7);color:var(--foreground);text-decoration:none;border-radius:var(--radius-small);transition:background-color var(--transition-fast);&:is(:hover,[aria-current]){background-color:var(--accent)}}details{border:none;overflow:visible;+details{margin-top:0}&[open] summary{border-bottom:none}>ul{margin-inline-start:var(--space-4);padding:var(--space-1) 0}}summary{justify-content:flex-start;padding:var(--space-2) var(--space-3);font-size:var(--text-7);border-radius:var(--radius-small);&:after{width:.75rem;height:.75rem;margin-inline-start:auto}}}}}nav[data-topnav]{position:fixed;inset:0 0 auto;z-index:5;min-height:var(--_nh);display:flex;align-items:center;gap:var(--space-3);padding:var(--space-2) var(--space-4);background-color:var(--background);border-bottom:1px solid var(--border);box-shadow:var(--shadow-small)}:is([data-sidebar-toggle],[data-sidebar-header]){display:none}[data-sidebar-toggle]{padding:0 var(--space-1);background:none;border:1px solid var(--border);border-radius:var(--radius-small);cursor:pointer}@media(min-width:769px){[data-sidebar-layout=always] [data-sidebar-toggle]{display:inline-block}[data-sidebar-layout=always]{transition:grid-template-columns var(--transition);>aside[data-sidebar]{transform:translate(0);opacity:1;transition:transform var(--transition),opacity var(--transition),visibility var(--transition)}}[data-sidebar-layout=always][data-sidebar-open]{grid-template-columns:0px 1fr;gap:0;>aside[data-sidebar]{overflow:hidden;min-width:0;transform:translate(-100%);opacity:0;visibility:hidden;border-inline-end:none}}}@media(max-width:768px){[data-sidebar-layout]{grid-template-columns:1fr;>aside[data-sidebar]{position:fixed;left:0;width:16rem;transform:translate(-100%);transition:transform var(--transition);box-shadow:var(--shadow-large)}&[data-sidebar-open]>aside[data-sidebar]{transform:translate(0)}}[data-sidebar-toggle]{display:inline-block}[data-sidebar-header]{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--border)}}}@layer components{[role=status].skeleton{--_c: light-dark( color-mix(in srgb, var(--muted) 30%, white), color-mix(in srgb, var(--muted) 90%, var(--foreground)) );margin-block-end:var(--space-3);background:var(--muted);border-radius:var(--radius-medium);animation:anim 2s infinite;background-size:200% 100%;background-image:linear-gradient(90deg,var(--muted) 0%,var(--_c) 50%,var(--muted) 100%);&.box{width:4rem;height:4rem}&.line{height:1rem;width:100%}}[role=status].skeleton:last-child{margin-block-end:0}@keyframes anim{0%{background-position:200% 0}to{background-position:-200% 0}}}@layer components{[data-tooltip]{position:relative}[data-tooltip]:before,[data-tooltip]:after{position:absolute;inset-inline-start:50%;opacity:0;visibility:hidden;transition:opacity var(--transition-fast),transform var(--transition-fast),visibility var(--transition-fast);pointer-events:none;z-index:1000}[data-tooltip]:after{content:attr(data-tooltip);inset-block-end:calc(100% + 10px);transform:translate(-50%) translateY(4px);padding:var(--space-2) var(--space-3);font-size:var(--text-7);line-height:1;white-space:nowrap;background:var(--foreground);color:var(--background);border-radius:var(--radius-medium)}[data-tooltip]:before{content:"";inset-block-end:calc(100% - 5px);transform:translate(-50%) translateY(4px);border:8px solid transparent;border-top-color:var(--foreground)}[data-tooltip]:is(:hover,:focus-visible):before,[data-tooltip]:is(:hover,:focus-visible):after{opacity:1;visibility:visible;transition-delay:.7s;transform:translate(-50%) translateY(0)}}@layer utilities{.text-left{text-align:start}.text-center{text-align:center}.text-right{text-align:end}.text-light{color:var(--muted-foreground)}.text-lighter{color:var(--faint-foreground)}.flex{display:flex}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.hstack{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;align-content:flex-start;height:auto}.vstack{display:flex;flex-direction:column;gap:var(--space-3)}.gap-1{gap:var(--space-1)}.gap-2{gap:var(--space-2)}.gap-4{gap:var(--space-4)}.mt-2{margin-block-start:var(--space-2)}.mt-4{margin-block-start:var(--space-4)}.mt-6{margin-block-start:var(--space-6)}.mb-2{margin-block-end:var(--space-2)}.mb-4{margin-block-end:var(--space-4)}.mb-6{margin-block-end:var(--space-6)}.p-4{padding:var(--space-4)}.w-100{width:100%}ul,ol{&.unstyled{list-style:none;padding-inline-start:0;margin-inline-start:0}}} diff --git a/src/app/resources/browser/assets/css/welcome.css b/src/app/resources/browser/assets/css/welcome.css deleted file mode 100644 index 036c1a40..00000000 --- a/src/app/resources/browser/assets/css/welcome.css +++ /dev/null @@ -1,158 +0,0 @@ -/* Custom styles for Zeal's built-in browser pages. Depends on oat.min.css. */ - -body { - user-select: none; - min-height: 100dvh; - display: flex; - flex-direction: column; - align-items: center; -} - -.page { - flex: 1; - display: grid; - grid-template-columns: 1fr 1.5fr; - align-content: center; - gap: 1.5rem 3rem; - max-width: 720px; - width: 100%; - padding: 2rem; -} - -.brand { - display: flex; - flex-direction: column; - justify-content: center; - gap: 0.25rem; -} - -.brand h1 { - font-size: 2rem; - line-height: 1.1; -} - -.brand .version { - font-weight: 300; - opacity: 0.5; - margin-left: 0.3rem; - font-size: 1rem; -} - -.brand > p { - opacity: 0.6; -} - -.actions { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.actions > h2 { - font-size: 0.7rem; - text-transform: uppercase; - letter-spacing: 0.07em; - opacity: 0.45; - margin-top: 0.5rem; - margin-bottom: 0; -} - -.command-block { - cursor: pointer; - padding: 0.625rem 0.875rem; - border-radius: 6px; - border: 1px solid #e2e8f0; - transition: - border-color 0.15s, - background 0.15s; -} - -.command-block:hover { - border-color: #94a3b8; - background: #f8fafc; -} - -.command-block h3 { - font-size: 0.875rem; - font-weight: 600; - margin-bottom: 0.125rem; -} - -.command-block p { - font-size: 0.775rem; - opacity: 0.55; - margin: 0; -} - -.footer { - display: flex; - flex-direction: column; - align-items: center; - gap: 0.625rem; - width: 100%; - max-width: 720px; - padding: 1rem 2rem; - border-top: 1px solid #e2e8f0; -} - -.icon-links { - display: flex; - gap: 1.25rem; - align-items: center; -} - -.icon-link { - cursor: pointer; - display: inline-flex; - color: inherit; - text-decoration: none; - opacity: 0.45; - transition: opacity 0.15s; -} - -.icon-link:hover { - opacity: 0.85; - text-decoration: none; -} - -.icon-link svg { - width: 1.2rem; - height: 1.2rem; -} - -.report-link { - display: inline-flex; - align-items: center; - gap: 0.3rem; - font-size: 0.75rem; - cursor: pointer; - opacity: 0.4; - transition: opacity 0.15s; - text-decoration: none; - color: inherit; -} - -.report-link:hover { - opacity: 0.7; - text-decoration: none; -} - -.report-link svg { - width: 0.85rem; - height: 0.85rem; -} - -@media (prefers-color-scheme: dark) { - .command-block { - border-color: #334155; - } - - .command-block:hover { - border-color: #475569; - background: #1e293b; - } - - .footer { - border-color: #334155; - } -} diff --git a/src/app/resources/browser/fonts/GoogleSansCode-Italic.woff2 b/src/app/resources/browser/fonts/GoogleSansCode-Italic.woff2 new file mode 100644 index 00000000..dea8b42c Binary files /dev/null and b/src/app/resources/browser/fonts/GoogleSansCode-Italic.woff2 differ diff --git a/src/app/resources/browser/fonts/GoogleSansCode.woff2 b/src/app/resources/browser/fonts/GoogleSansCode.woff2 new file mode 100644 index 00000000..fcd5368b Binary files /dev/null and b/src/app/resources/browser/fonts/GoogleSansCode.woff2 differ diff --git a/src/app/resources/browser/fonts/Inter-Italic.woff2 b/src/app/resources/browser/fonts/Inter-Italic.woff2 new file mode 100644 index 00000000..b3530f3f Binary files /dev/null and b/src/app/resources/browser/fonts/Inter-Italic.woff2 differ diff --git a/src/app/resources/browser/fonts/Inter.woff2 b/src/app/resources/browser/fonts/Inter.woff2 new file mode 100644 index 00000000..5a8d3e72 Binary files /dev/null and b/src/app/resources/browser/fonts/Inter.woff2 differ diff --git a/src/app/resources/browser/fonts/SourceSerif4-Italic.woff2 b/src/app/resources/browser/fonts/SourceSerif4-Italic.woff2 new file mode 100644 index 00000000..9fcf4743 Binary files /dev/null and b/src/app/resources/browser/fonts/SourceSerif4-Italic.woff2 differ diff --git a/src/app/resources/browser/fonts/SourceSerif4.woff2 b/src/app/resources/browser/fonts/SourceSerif4.woff2 new file mode 100644 index 00000000..cca344bf Binary files /dev/null and b/src/app/resources/browser/fonts/SourceSerif4.woff2 differ diff --git a/src/app/resources/browser/new-tab.html b/src/app/resources/browser/new-tab.html new file mode 100644 index 00000000..2615be1f --- /dev/null +++ b/src/app/resources/browser/new-tab.html @@ -0,0 +1,133 @@ + + + + + + + + New tab + + + + +
+ +
+
+ +

Zeal

+
+

Your offline reference library

+
+ + + + + +
+

Get started

+
+
+
+

Docset Library

+

Install and update docsets

+
+ +
+
+
+
+ + + + + + + + + diff --git a/src/app/resources/browser/not-found.html b/src/app/resources/browser/not-found.html new file mode 100644 index 00000000..acc2ebaf --- /dev/null +++ b/src/app/resources/browser/not-found.html @@ -0,0 +1,140 @@ + + + + + + + + Page not found + + + + +
+ +
+ +

Page not found

+

This page doesn't exist.

+
+ + +
+

Troubleshoot

+
+
+
+

Docset Library

+

Update or reinstall docsets

+
+ +
+
+
+ + +
+

Get in touch

+
+
+
+

Discord

+

Chat with the developers and other users

+
+ +
+
+
+

GitHub

+

Source code and issues

+
+ +
+
+
+
+ + + + + + + + + diff --git a/src/app/resources/browser/styles/highlight.css b/src/app/resources/browser/styles/highlight.css new file mode 100644 index 00000000..57fbca61 --- /dev/null +++ b/src/app/resources/browser/styles/highlight.css @@ -0,0 +1,18 @@ +/* Highlight on navigation to an anchor. */ +@keyframes targetNavigatedAnimation { + from { + background: #fff; + } + 50% { + background: #ff0; + } + to { + background: #fff; + } +} + +@media (prefers-reduced-motion: no-preference) { + *:target { + animation: targetNavigatedAnimation 0.5s linear; + } +} diff --git a/src/app/resources/browser/styles/manual.css b/src/app/resources/browser/styles/manual.css new file mode 100644 index 00000000..c0e90853 --- /dev/null +++ b/src/app/resources/browser/styles/manual.css @@ -0,0 +1,191 @@ +/* Reference-manual styles layered on .prose: reading column, section + headings, tables, callouts, menu-path chips. Scoped to .doc. + Per-surface knobs: --docw, --doc-pad, --doc-anchor (defaults below). */ + +.doc { + max-width: var(--docw, 760px); + margin: 0 auto; + padding: var(--doc-pad, 8px 0 0); +} + +.doc section { + padding-top: 2rem; + scroll-margin-top: var(--doc-anchor, 84px); +} + +.doc section > h2 { + font-family: var(--display); + font-weight: 600; + font-size: 1.5rem; + line-height: 1.18; + letter-spacing: -0.01em; + font-variation-settings: "opsz" 40; + padding-bottom: 0.5rem; + border-bottom: 1px solid var(--rule); + margin: 0 0 1.1rem; + display: flex; + align-items: baseline; + gap: 0.6rem; +} +.doc section > h2 .nn { + font-family: var(--mono); + font-size: 0.72rem; + font-weight: 500; + color: var(--red); + letter-spacing: 0.06em; +} +.doc h3 { + font-family: var(--body); + font-weight: 600; + font-size: 0.98rem; + letter-spacing: 0; + margin: 1.6rem 0 0.5rem; + color: var(--ink); +} + +/* Tables */ +.doc .table-wrap { + width: 100%; + overflow-x: auto; + margin: 0.4rem 0 1.1rem; +} +.doc table { + width: 100%; + border-collapse: collapse; + font-size: 0.86rem; +} +.doc caption { + text-align: left; + font-size: 0.7rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--ink-softer); + padding-bottom: 0.5rem; +} +.doc th { + text-align: left; + font-weight: 600; + color: var(--ink-soft); + padding: 0.5rem 0.9rem; + border-bottom: 1px solid var(--rule); + white-space: nowrap; +} +.doc td { + padding: 0.5rem 0.9rem; + border-bottom: 1px solid var(--rule-soft); + vertical-align: top; + line-height: 1.55; +} +.doc td:first-child { + color: var(--ink); +} +.doc tbody tr:last-child td { + border-bottom: 0; +} +.doc .keys { + display: inline-flex; + gap: 0.25rem; + flex-wrap: wrap; + vertical-align: baseline; + align-items: center; +} +.doc .keys .sep { + color: var(--ink-softer); + font-size: 0.8rem; + padding: 0 0.1rem; +} +.doc td code { + white-space: normal; + overflow-wrap: anywhere; +} +.doc td .tok { + color: var(--accent); +} +/* Stack link cells as blocks; display:flex on a breaks fixed-table row alignment. */ +.doc .td-links a { + display: block; + font-family: var(--mono); + font-size: 0.8rem; + color: var(--accent); + text-decoration: none; + overflow-wrap: anywhere; +} +.doc .td-links a + a { + margin-top: 3px; +} +.doc .td-links a:hover { + text-decoration: underline; + text-underline-offset: 2px; +} + +/* Callout */ +.doc .callout { + background: var(--callout); + border: 1px solid var(--callout-border); + box-shadow: var(--callout-shadow); + border-radius: 6px; + padding: 0.85rem 1rem; + margin: 0.4rem 0 1.1rem; + font-size: 0.85rem; + color: var(--ink-soft); + line-height: 1.55; +} +.doc .callout strong { + color: var(--ink); + font-weight: 600; +} +.doc .callout a { + color: var(--accent); + text-decoration: underline; + text-decoration-color: var(--rule); + text-underline-offset: 2px; +} +.doc .callout a:hover { + color: var(--accent-hover); + text-decoration-color: var(--accent-hover); +} + +/* Menu-path chip */ +.doc .menu-path { + display: inline-flex; + align-items: center; + gap: 0.5rem; + flex-wrap: wrap; + margin: 0 0 0.85rem; +} +.doc .menu-path .seg { + font-family: var(--mono); + font-size: 0.8rem; + color: var(--ink); + background: var(--deep); + border: 1px solid var(--rule); + border-bottom-width: 2px; + border-radius: 3px; + padding: 2px 8px; +} +.doc .menu-path .arr { + color: var(--ink-softer); +} + +/* Print */ +@media print { + .doc { + max-width: none; + } + .doc section { + padding-top: 1.25rem; + scroll-margin-top: 0; + } + .doc .table-wrap { + overflow: visible; + } + .doc h2, + .doc h3 { + break-after: avoid; + } + .doc tr, + .doc .callout { + break-inside: avoid; + } +} diff --git a/src/app/resources/browser/styles/pages.css b/src/app/resources/browser/styles/pages.css new file mode 100644 index 00000000..cd65d6c8 --- /dev/null +++ b/src/app/resources/browser/styles/pages.css @@ -0,0 +1,530 @@ +/* Styles for Zeal's built-in browser pages (new-tab, not-found, help). + Offline, no frameworks; conservative CSS for Chromium 102. */ +@import "tokens.css"; +@import "prose.css"; +@import "manual.css"; + +/* Bundled fonts */ +@font-face { + font-family: "Inter"; + src: url("../fonts/Inter.woff2") format("woff2"); + font-weight: 100 900; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: "Inter"; + src: url("../fonts/Inter-Italic.woff2") format("woff2"); + font-weight: 100 900; + font-style: italic; + font-display: swap; +} +@font-face { + font-family: "Source Serif 4"; + src: url("../fonts/SourceSerif4.woff2") format("woff2"); + font-weight: 200 900; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: "Source Serif 4"; + src: url("../fonts/SourceSerif4-Italic.woff2") format("woff2"); + font-weight: 200 900; + font-style: italic; + font-display: swap; +} +@font-face { + font-family: "Google Sans Code"; + src: url("../fonts/GoogleSansCode.woff2") format("woff2"); + font-weight: 300 800; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: "Google Sans Code"; + src: url("../fonts/GoogleSansCode-Italic.woff2") format("woff2"); + font-weight: 300 800; + font-style: italic; + font-display: swap; +} + +/* App-only tokens */ +:root { + /* Declare both schemes so Qt WebEngine does not auto-invert these pages; + they theme themselves, and inversion would recolour the brand-red fills. */ + color-scheme: light dark; + --shadow-soft: 0 1px 2px rgb(31 27 20 / 0.05); + --maxw: 460px; + /* manual.css reading-column knobs */ + --docw: 760px; + --doc-pad: 1rem clamp(1rem, 4vw, 2rem) 0; + --doc-anchor: 4.5rem; +} +@media (prefers-color-scheme: dark) { + :root { + --shadow-soft: 0 1px 2px rgb(0 0 0 / 0.4); + } +} + +/* Reset / base */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + font-size: 16px; +} + +body { + margin: 0; + min-height: 100vh; + font-family: var(--body); + font-optical-sizing: auto; + font-size: var(--type-body-size); + line-height: 1.55; + color: var(--ink); + background: var(--paper); + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +/* Chrome pages should not feel like a selectable document. */ +body.screen { + user-select: none; +} + +h1, +h2, +h3 { + font-family: var(--display); + font-weight: 600; + letter-spacing: -0.01em; + line-height: 1.18; + margin: 0; +} + +p { + margin: 0; + text-wrap: pretty; +} + +a { + color: var(--accent); + text-decoration: underline; + text-underline-offset: 2px; + text-decoration-thickness: 1px; + cursor: pointer; + transition: color 0.12s cubic-bezier(0.4, 0, 0.2, 1); +} +a:hover { + color: var(--accent-hover); +} + +code, +kbd, +samp { + font-family: var(--mono); +} + +kbd { + display: inline-block; + min-width: 1.1em; + padding: 2px 6px 1px; + font-family: var(--mono); + font-size: 0.78em; + font-weight: 500; + text-align: center; + color: var(--ink); + background: var(--deep); + border: 1px solid var(--rule); + border-bottom-width: 2px; + border-radius: 3px; + white-space: nowrap; +} + +/* Focus */ +:focus { + outline: none; +} +:focus-visible { + outline: 2px solid var(--ring); + outline-offset: 2px; + border-radius: 3px; +} + +/* New-tab / not-found */ +.screen { + display: flex; + flex-direction: column; + align-items: center; +} + +.page { + flex: 1 1 auto; + width: 100%; + max-width: var(--maxw); + display: flex; + flex-direction: column; + justify-content: center; + gap: 1.75rem; + padding: 3rem 1.75rem 1.5rem; +} + +/* Brand lockup */ +.brand { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; +} +.lockup { + display: flex; + align-items: center; + gap: 0.6rem; +} +.lockup .logo { + width: 36px; + height: 36px; + flex: none; + border-radius: 3.5px; + display: block; + box-shadow: var(--shadow-soft); +} +.wordmark { + font-family: var(--display); + font-weight: 600; + font-size: 1.6rem; + font-variation-settings: "opsz" 36; + line-height: 1; + letter-spacing: -0.01em; +} +.tagline { + font-family: var(--display); + font-style: italic; + font-weight: 400; + font-size: 1.1rem; + line-height: 1.2; + color: var(--ink-soft); + font-variation-settings: "opsz" 36; +} + +/* Overline labels */ +.overline { + font-family: var(--body); + font-size: 0.66rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.09em; + color: var(--ink-softer); + margin: 0 0 0.7rem; +} + +/* Not-found heading */ +.headline { + display: flex; + flex-direction: column; + gap: 0.4rem; +} +.headline h1 { + font-size: 1.75rem; + font-variation-settings: "opsz" 36; +} +.headline p { + color: var(--ink-soft); + font-size: 0.875rem; +} + +/* Search hints */ +.search-lead { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.95rem; + color: var(--ink); + margin-bottom: 0.9rem; +} +.search-lead .ico { + width: 15px; + height: 15px; + flex: none; + color: var(--accent); +} + +.hints { + display: grid; + grid-template-columns: max-content 1fr; + column-gap: 1rem; + row-gap: 0.5rem; + align-items: baseline; +} +.hints .syntax { + display: inline-flex; + align-items: center; + gap: 0.2rem; +} +.hints .syntax code { + font-size: 0.8rem; + color: var(--ink); + background: var(--prefix-box); + border: 1px solid var(--rule-soft); + border-radius: 3px; + padding: 2px 7px; + white-space: nowrap; + user-select: text; /* copyable */ + cursor: text; +} +.hints .syntax .tok { + color: var(--accent); +} +.hints .desc { + font-size: 0.82rem; + color: var(--ink-soft); + line-height: 1.4; +} + +/* Action cards */ +.cards { + display: flex; + flex-direction: column; + gap: 0.6rem; +} +.card { + display: flex; + align-items: center; + gap: 0.85rem; + width: 100%; + text-align: left; + padding: 0.8rem 0.95rem; + background: var(--card); + border: 1px solid var(--rule-soft); + border-radius: 6px; + cursor: pointer; + color: inherit; + text-decoration: none; + transition: + border-color 0.12s cubic-bezier(0.4, 0, 0.2, 1), + background 0.12s cubic-bezier(0.4, 0, 0.2, 1); +} +.card:hover { + border-color: var(--accent); + background: var(--card-hover); +} +.card .body { + flex: 1 1 auto; + min-width: 0; +} +.card .body h3 { + font-family: var(--body); + font-weight: 600; + font-size: 0.9rem; + letter-spacing: 0; + margin: 0 0 1px; +} +.card .body p { + font-size: 0.8rem; + color: var(--ink-soft); + margin: 0; + max-width: none; +} +.card .arrow { + width: 16px; + height: 16px; + flex: none; + color: var(--ink-softer); + transition: + color 0.12s, + transform 0.12s cubic-bezier(0.4, 0, 0.2, 1); +} +.card:hover .arrow { + color: var(--accent); + transform: translateX(2px); +} + +/* Footer */ +.footer { + width: 100%; + max-width: var(--maxw); + display: flex; + flex-direction: column; + align-items: center; + gap: 0.7rem; + padding: 1.25rem 1.75rem; + border-top: 1px solid var(--rule-soft); +} +.icon-links { + display: flex; + gap: 1.4rem; + align-items: center; +} +.icon-link { + display: inline-flex; + color: var(--ink-soft); + opacity: 0.65; + text-decoration: none; + transition: + color 0.12s, + opacity 0.12s; +} +.icon-link:hover { + color: var(--accent); + opacity: 1; +} +.icon-link svg { + width: 1.15rem; + height: 1.15rem; + display: block; +} + +/* Help page */ +html.manual-root { + scroll-behavior: smooth; +} + +.topnav { + position: sticky; + top: 0; + z-index: 10; + display: flex; + align-items: center; + gap: 1rem; + padding: 0.7rem clamp(1rem, 4vw, 2rem); + background: var(--cream); + border-bottom: 1px solid var(--rule-soft); +} +.topnav .home { + display: inline-flex; + align-items: center; + gap: 0.55rem; + text-decoration: none; + color: var(--ink); + margin-right: auto; +} +.topnav .home .logo { + width: 26px; + height: 26px; + border-radius: 2.5px; + display: block; +} +.topnav .home .title { + font-family: var(--display); + font-weight: 600; + font-size: 1.05rem; + font-variation-settings: "opsz" 28; +} +.topnav .home .title .sub { + color: var(--ink-soft); + font-weight: 400; +} +.topnav nav { + display: flex; + gap: 0.3rem; + flex-wrap: wrap; +} +.topnav nav a { + font-size: 0.82rem; + color: var(--ink-soft); + text-decoration: none; + padding: 0.3rem 0.6rem; + border-radius: 4px; + transition: + color 0.12s, + background 0.12s; +} +.topnav nav a:hover { + color: var(--accent); + background: var(--nav-hover, var(--manila)); +} + +.help-links { + display: flex; + flex-direction: column; + gap: 0.6rem; + margin: 0.4rem 0 0; +} + +.doc .footer { + max-width: none; + margin-top: 3rem; +} + +/* Reduced motion */ +@media (prefers-reduced-motion: reduce) { + * { + animation: none !important; + transition: none !important; + } + html.manual-root { + scroll-behavior: auto; + } +} + +/* Print */ +@page { + margin: 1.6cm; +} +@media print { + :root { + --paper: #fff; + --cream: #fff; + --card: #fff; + --manila: #fff; + --deep: #f3f3f3; + --ink: #000; + --ink-soft: #333; + --ink-softer: #555; + --rule: #bbb; + --rule-soft: #ddd; + --accent: #75201a; + --prefix-box: #f3f3f3; + --callout: #fff; + --callout-border: #bbb; + --callout-shadow: none; + } + html, + body { + background: #fff; + height: auto; + min-height: 0; + overflow: visible; + } + html.manual-root { + scroll-behavior: auto; + } + .topnav { + position: static; + padding-left: 0; + padding-right: 0; + border-bottom: 1px solid #bbb; + } + .topnav nav { + display: none; + } + .footer .icon-links { + display: none; + } + .doc { + max-width: none; + padding: 0 0 1rem; + } + .doc section { + padding-top: 1.25rem; + } + .table-wrap { + overflow: visible; + } + h2, + h3 { + break-after: avoid; + } + tr, + .callout, + .card { + break-inside: avoid; + } + a { + color: #000; + text-decoration: none; + } +} diff --git a/src/app/resources/browser/styles/prose.css b/src/app/resources/browser/styles/prose.css new file mode 100644 index 00000000..87997b7e --- /dev/null +++ b/src/app/resources/browser/styles/prose.css @@ -0,0 +1,81 @@ +/* Shared reading-text styles (lede, paragraphs, inline code, kbd, lists). + Scoped to .prose; consumes the tokens in tokens.css. */ + +.prose { + color: var(--ink); + line-height: 1.6; + text-wrap: pretty; +} + +/* Lede */ +.prose .lede { + font-family: var(--display); + font-variation-settings: "opsz" 24; + font-size: 1.18rem; + line-height: 1.5; + letter-spacing: -0.1px; + color: var(--ink); + margin: 2rem 0 0.5rem; +} + +/* Paragraphs */ +.prose p { + margin: 0 0 0.85rem; + color: var(--ink); + line-height: 1.6; +} +.prose p.muted { + color: var(--ink-soft); + font-size: 0.92rem; +} +.prose strong { + font-weight: 600; +} + +/* Inline links */ +.prose a.inline { + color: var(--accent); + text-decoration: underline; + text-decoration-color: var(--rule); + text-underline-offset: 2px; +} +.prose a.inline:hover { + color: var(--accent-hover); + text-decoration-color: var(--accent-hover); +} + +/* Inline code */ +.prose code { + font-family: var(--mono); + font-size: 0.86em; + background: var(--deep); + border: 1px solid var(--rule-soft); + border-radius: 3px; + padding: 1px 5px; +} + +/* Keyboard keys */ +.prose kbd { + display: inline-block; + min-width: 1.1em; + padding: 2px 6px 1px; + font-family: var(--mono); + font-size: 0.78em; + font-weight: 500; + text-align: center; + color: var(--ink); + background: var(--deep); + border: 1px solid var(--rule); + border-bottom-width: 2px; + border-radius: 3px; + white-space: nowrap; +} + +/* Lists */ +.prose ul { + margin: 0 0 0.95rem; + padding-left: 1.1rem; +} +.prose li { + margin: 0.25rem 0; +} diff --git a/src/app/resources/browser/styles/tokens.css b/src/app/resources/browser/styles/tokens.css new file mode 100644 index 00000000..d74de1e2 --- /dev/null +++ b/src/app/resources/browser/styles/tokens.css @@ -0,0 +1,278 @@ +/* Design tokens shared by Zeal's built-in browser pages. + Light by default; dark via prefers-color-scheme and [data-theme="dark"]. */ + +:root { + /* Surfaces */ + --paper: #fbf7ec; + --card: #f5ecd1; + --card-hover: #faf4e2; + --deep: #e0d6bb; + /* Legacy surface aliases */ + --cream: var(--card); + --manila: var(--card); + + /* Component surfaces */ + --prefix-box: #efe7d1; + --callout: #f5ecd1; + --callout-border: var(--rule-soft); + --callout-shadow: none; + --nav-hover: #e9dfc5; + + /* Text */ + --ink: #1f1b14; + --ink-soft: #55503f; + --ink-softer: #655f4a; + + /* Rules */ + --rule: #c9bfa3; + --rule-soft: #d9d0b8; + + /* Brand accent — --accent for text, --accent-solid for fills */ + --accent: #b4322b; + --accent-hover: #75201a; + --accent-solid: #b4322b; + --accent-solid-hover: #75201a; + --on-accent: #faf4e2; + + /* Legacy --red aliases */ + --red: var(--accent); + --on-red: var(--on-accent); + + /* AppStream store-listing branding only */ + --brand-banner-light: #f1c3a7; + --brand-banner-dark: #75201a; + + /* Taxonomy accents */ + --blue: #3e5f7a; + --green: #4a7a4a; + --amber: #a86a14; + --plum: #6b4a82; + + /* Semantic aliases */ + --background: var(--paper); + --foreground: var(--ink); + --foreground-soft: var(--ink-soft); + --foreground-softer: var(--ink-softer); + --surface-card: var(--card); + --surface-inset: var(--deep); + --border: var(--rule); + --border-soft: var(--rule-soft); + + --primary: var(--accent-solid); + --primary-foreground: var(--on-accent); + --danger: var(--accent-solid); + --danger-foreground: var(--on-accent); + --success: var(--green); + --success-foreground: var(--on-accent); + --warning: var(--amber); + --warning-foreground: var(--on-accent); + --info: var(--blue); + --info-foreground: var(--on-accent); + + --ring: var(--accent); + + /* Type families */ + --display: "Source Serif 4", "Lora", Georgia, serif; + --body: + "Inter", system-ui, -apple-system, "Segoe UI", "Helvetica Neue", Helvetica, + Arial, sans-serif; + --mono: + "Google Sans Code", "JetBrains Mono", ui-monospace, SFMono-Regular, + "SF Mono", Menlo, monospace; + + --font-sans: var(--body); + --font-mono: var(--mono); + + --display-opsz: 60; + --display-opsz-text: 40; + + --font-regular: 400; + --font-medium: 500; + --font-semibold: 600; + --font-bold: 700; + + /* Type scale */ + --text-1: clamp(2.5rem, 2rem + 2.2vw, 3.5rem); + --text-2: clamp(1.75rem, 1.5rem + 1.1vw, 2.25rem); + --text-3: clamp(1.5rem, 1.3rem + 0.8vw, 1.875rem); + --text-4: clamp(1.25rem, 1.1rem + 0.5vw, 1.5rem); + --text-5: 1.125rem; + --text-6: 1rem; + --text-7: 0.875rem; + --text-8: 0.75rem; + --text-9: 0.6875rem; + + --leading-tight: 1.2; + --leading-snug: 1.35; + --leading-normal: 1.5; + --leading-loose: 1.65; + + /* Type roles */ + --type-display-font: var(--display); + --type-display-weight: 400; + --type-display-size: var(--text-1); + --type-display-lh: var(--leading-tight); + + --type-h1-font: var(--display); + --type-h1-weight: 600; + --type-h1-size: var(--text-2); + --type-h2-font: var(--display); + --type-h2-weight: 600; + --type-h2-size: var(--text-3); + --type-h3-font: var(--body); + --type-h3-weight: 600; + --type-h3-size: var(--text-4); + + --type-body-font: var(--body); + --type-body-weight: 400; + --type-body-size: 0.9375rem; /* 15px */ + --type-body-lh: var(--leading-normal); + + --type-small-font: var(--body); + --type-small-size: var(--text-7); + --type-meta-font: var(--mono); + --type-meta-size: var(--text-8); + --type-meta-color: var(--ink-soft); + --type-code-font: var(--mono); + --type-code-size: 0.875em; + + /* Spacing */ + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 0.75rem; + --space-4: 1rem; + --space-5: 1.25rem; + --space-6: 1.5rem; + --space-8: 2rem; + --space-10: 2.5rem; + --space-12: 3rem; + --space-14: 3.5rem; + --space-16: 4rem; + --space-18: 4.5rem; + --space-24: 6rem; + + /* Radii */ + --radius-small: 2px; + --radius-medium: 3px; + --radius-large: 6px; + + /* Elevation */ + --shadow-small: 0 1px 2px 0 rgb(31 27 20 / 0.08); + --shadow-card: + 0 2px 4px -1px rgb(31 27 20 / 0.06), 0 8px 24px -8px rgb(31 27 20 / 0.1); + + /* Motion */ + --transition-fast: 0.12s cubic-bezier(0.4, 0, 0.2, 1); + --transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1); + + --z-dropdown: 50; + --z-modal: 200; +} + +/* Dark theme — declared twice: prefers-color-scheme for OS-auto, and + [data-theme] for the manual toggle (a media query can't OR a selector). */ +@media (prefers-color-scheme: dark) { + :root { + --paper: #1b1308; + --card: #261e12; + --card-hover: #352a1a; + --deep: #322817; + --cream: var(--card); + --manila: var(--card); + + --prefix-box: #15100a; + --callout: #160f07; + --callout-border: var(--rule); + --callout-shadow: inset 0 1px 3px rgb(0 0 0 / 0.5); + --nav-hover: #1b1308; + + --ink: #ddd2b6; + --ink-soft: #b3a892; + --ink-softer: #9a9180; + + --rule: #6b6253; + --rule-soft: #554c3f; + + --accent: #e8534a; + --accent-hover: #f0754f; + --accent-solid: #b4322b; + --accent-solid-hover: #bd3a2f; + --on-accent: #faf4e2; + + --blue: #5486ac; + --green: #5f8d64; + --amber: #b07932; + --plum: #8a68a0; + + --shadow-small: 0 1px 2px 0 rgb(0 0 0 / 0.4); + --shadow-card: + 0 2px 4px -1px rgb(0 0 0 / 0.3), 0 8px 24px -8px rgb(0 0 0 / 0.55); + } + /* Force light under a dark OS */ + :root[data-theme="light"] { + --paper: #fbf7ec; + --card: #f5ecd1; + --card-hover: #faf4e2; + --deep: #e0d6bb; + --cream: var(--card); + --manila: var(--card); + --prefix-box: #efe7d1; + --callout: #f5ecd1; + --callout-border: var(--rule-soft); + --callout-shadow: none; + --nav-hover: #e9dfc5; + --ink: #1f1b14; + --ink-soft: #55503f; + --ink-softer: #655f4a; + --rule: #c9bfa3; + --rule-soft: #d9d0b8; + --accent: #b4322b; + --accent-hover: #75201a; + --accent-solid: #b4322b; + --accent-solid-hover: #75201a; + --on-accent: #faf4e2; + --blue: #3e5f7a; + --green: #4a7a4a; + --amber: #a86a14; + --plum: #6b4a82; + --shadow-small: 0 1px 2px 0 rgb(31 27 20 / 0.08); + --shadow-card: + 0 2px 4px -1px rgb(31 27 20 / 0.06), 0 8px 24px -8px rgb(31 27 20 / 0.1); + } +} +:root[data-theme="dark"] { + --paper: #1b1308; + --card: #261e12; + --card-hover: #352a1a; + --deep: #322817; + --cream: var(--card); + --manila: var(--card); + + --prefix-box: #15100a; + --callout: #160f07; + --callout-border: var(--rule); + --callout-shadow: inset 0 1px 3px rgb(0 0 0 / 0.5); + --nav-hover: #1b1308; + + --ink: #ddd2b6; + --ink-soft: #b3a892; + --ink-softer: #9a9180; + + --rule: #6b6253; + --rule-soft: #554c3f; + + --accent: #e8534a; + --accent-hover: #f0754f; + --accent-solid: #b4322b; + --accent-solid-hover: #bd3a2f; + --on-accent: #faf4e2; + + --blue: #5486ac; + --green: #5f8d64; + --amber: #b07932; + --plum: #8a68a0; + + --shadow-small: 0 1px 2px 0 rgb(0 0 0 / 0.4); + --shadow-card: + 0 2px 4px -1px rgb(0 0 0 / 0.3), 0 8px 24px -8px rgb(0 0 0 / 0.55); +} diff --git a/src/app/resources/browser/welcome.html b/src/app/resources/browser/welcome.html deleted file mode 100644 index 897d4a27..00000000 --- a/src/app/resources/browser/welcome.html +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - - New tab - - - - - -
-
-

Zeal

-

Your offline reference library

-
- -
-

Get started

-
-

Docset Library

-

Install and update docsets

-
-
-

Preferences

-

Adjust application settings

-
-

Get in touch

-
-

Discord

-

Chat with the developers and other users

-
-
-

GitHub

-

Contribute to the project

-
-
-
- - - - - - - - diff --git a/src/app/zeal.qrc b/src/app/zeal.qrc index d55e4b89..599b2ab9 100644 --- a/src/app/zeal.qrc +++ b/src/app/zeal.qrc @@ -4,11 +4,19 @@ ../../assets/freedesktop/sc-apps-zeal-tray.svg - resources/browser/404.html - resources/browser/assets/css/highlight.css - resources/browser/assets/css/oat.min.css - resources/browser/assets/css/welcome.css - resources/browser/welcome.html + resources/browser/new-tab.html + resources/browser/not-found.html + resources/browser/styles/highlight.css + resources/browser/styles/manual.css + resources/browser/styles/pages.css + resources/browser/styles/prose.css + resources/browser/styles/tokens.css + resources/browser/fonts/GoogleSansCode.woff2 + resources/browser/fonts/GoogleSansCode-Italic.woff2 + resources/browser/fonts/Inter.woff2 + resources/browser/fonts/Inter-Italic.woff2 + resources/browser/fonts/SourceSerif4.woff2 + resources/browser/fonts/SourceSerif4-Italic.woff2 ../../assets/tabler/app-window.svg diff --git a/src/libs/browser/settings.cpp b/src/libs/browser/settings.cpp index 61b29fd0..daac37ba 100644 --- a/src/libs/browser/settings.cpp +++ b/src/libs/browser/settings.cpp @@ -21,7 +21,7 @@ Q_LOGGING_CATEGORY(log, "zeal.browser.settings") using Qt::Literals::StringLiterals::operator""_L1; -constexpr auto HighlightOnNavigateCssPath = ":/browser/assets/css/highlight.css"_L1; +constexpr auto HighlightOnNavigateCssPath = ":/browser/styles/highlight.css"_L1; } // namespace QWebEngineProfile *Settings::m_webProfile = nullptr; diff --git a/src/libs/browser/webcontrol.cpp b/src/libs/browser/webcontrol.cpp index c926e3b4..83650967 100644 --- a/src/libs/browser/webcontrol.cpp +++ b/src/libs/browser/webcontrol.cpp @@ -48,7 +48,10 @@ WebControl::WebControl(QWidget *parent) m_webView->setToolTip(link); }); connect(m_webView, &QWebEngineView::titleChanged, this, &WebControl::titleChanged); - connect(m_webView, &QWebEngineView::urlChanged, this, &WebControl::urlChanged); + connect(m_webView, &QWebEngineView::urlChanged, this, [this](const QUrl &url) { + updateWebBridge(url); + emit urlChanged(url); + }); connect(m_webView, &WebView::zoomLevelChanged, this, &WebControl::zoomLevelChanged); // On a failed load that set no title, relabel the tab to signal the failure. @@ -148,18 +151,44 @@ void WebControl::setJavaScriptEnabled(bool enabled) } void WebControl::setWebBridgeObject(const QString &name, QObject *object) +{ + m_webBridgeName = name; + m_webBridgeObject = object; + + // Drop any existing channel so updateWebBridge() re-registers with the new object; it + // returns early when a channel is already present on an open qrc page. + if (QWebChannel *oldChannel = m_webView->page()->webChannel()) { + m_webView->page()->setWebChannel(nullptr); + oldChannel->deleteLater(); + } + + updateWebBridge(m_webView->url()); +} + +void WebControl::updateWebBridge(const QUrl &url) { QWebEnginePage *page = m_webView->page(); + if (url.scheme() != QLatin1String("qrc") || m_webBridgeName.isEmpty() || m_webBridgeObject == nullptr) { + if (QWebChannel *oldChannel = page->webChannel()) { + page->setWebChannel(nullptr); + oldChannel->deleteLater(); + } + return; + } + + if (page->webChannel() != nullptr) { + return; + } auto *channel = new QWebChannel(page); - channel->registerObject(name, object); - + channel->registerObject(m_webBridgeName, m_webBridgeObject); page->setWebChannel(channel); } void WebControl::load(const QUrl &url) { m_renderProcessReloadAttempts = 0; + updateWebBridge(url); m_webView->load(url); } diff --git a/src/libs/browser/webcontrol.h b/src/libs/browser/webcontrol.h index cd4dc07e..4fa4d11e 100644 --- a/src/libs/browser/webcontrol.h +++ b/src/libs/browser/webcontrol.h @@ -61,8 +61,12 @@ protected: private: friend class WebView; + void updateWebBridge(const QUrl &url); + WebView *m_webView = nullptr; SearchToolBar *m_searchToolBar = nullptr; + QString m_webBridgeName; + QObject *m_webBridgeObject = nullptr; int m_renderProcessReloadAttempts = 0; }; diff --git a/src/libs/browser/webpage.cpp b/src/libs/browser/webpage.cpp index e2cd7888..286b3514 100644 --- a/src/libs/browser/webpage.cpp +++ b/src/libs/browser/webpage.cpp @@ -18,6 +18,7 @@ #include #include #include +#include namespace Zeal::Browser { @@ -39,6 +40,26 @@ WebPage::WebPage(QObject *parent) // TODO: Use a dedicated content appearance signal instead of the catch-all updated(). applyBackgroundColor(); connect(Core::Application::instance()->settings(), &Core::Settings::updated, this, &WebPage::applyBackgroundColor); + + // Tabs open on the qrc new-tab page, which themes itself; start with ForceDarkMode off + // so its first render is not inverted. acceptNavigationRequest() sets it per navigated + // URL, and a theme change re-applies it for the current page. +#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) + settings()->setAttribute(QWebEngineSettings::ForceDarkMode, false); +#endif + connect(Core::Application::instance()->settings(), &Core::Settings::updated, this, [this]() { + applyForceDarkMode(url()); + updateInternalPageTheme(); + }); + + // qrc fallback pages (e.g. not-found) can load without the theme fragment that browsertab + // adds to the new-tab URL, so they start in the OS theme. Push Zeal's resolved theme once + // the page is ready; updateInternalPageTheme() no-ops for non-qrc pages. + connect(this, &QWebEnginePage::loadFinished, this, [this](bool ok) { + if (ok) { + updateInternalPageTheme(); + } + }); } void WebPage::applyBackgroundColor() @@ -46,10 +67,46 @@ void WebPage::applyBackgroundColor() setBackgroundColor(QApplication::palette().color(QPalette::Window)); } +void WebPage::applyForceDarkMode(const QUrl &pageUrl) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) + // qrc pages render their own theme (light, or hand-crafted dark via the data-theme + // attribute); ForceDarkMode exists to darken docset content, so keep it off for them. + const bool isInternal = pageUrl.scheme() == QStringLiteral("qrc"); + auto *appSettings = Core::Application::instance()->settings(); + settings()->setAttribute(QWebEngineSettings::ForceDarkMode, !isInternal && appSettings->isDarkModeEnabled()); +#else + Q_UNUSED(pageUrl) +#endif +} + +void WebPage::updateInternalPageTheme() +{ + // qrc pages bake their theme in at load (URL fragment + inline script), which does not + // react to a later theme change. Push the resolved theme to the open page so switching + // appearance updates it live, mirroring what the inline script does on load. + if (url().scheme() != QStringLiteral("qrc")) { + return; + } + + const QString theme = Core::Application::instance()->settings()->isDarkModeEnabled() ? QStringLiteral("dark") + : QStringLiteral("light"); + runJavaScript(QStringLiteral("var r = document.documentElement;" + "r.setAttribute('data-theme', '%1');" + "r.style.colorScheme = '%1';") + .arg(theme)); +} + bool WebPage::acceptNavigationRequest(const QUrl &requestUrl, QWebEnginePage::NavigationType type, bool isMainFrame) { Q_UNUSED(type) + // Keep ForceDarkMode (which darkens docset content) from inverting Zeal's own qrc + // pages; they theme themselves via the data-theme set from the URL fragment. + if (isMainFrame) { + applyForceDarkMode(requestUrl); + } + // Local elements are always allowed. if (Core::NetworkAccessManager::isLocalUrl(requestUrl)) { return true; @@ -74,10 +131,18 @@ bool WebPage::acceptNavigationRequest(const QUrl &requestUrl, QWebEnginePage::Na switch (appSettings->externalLinkPolicy) { case ExternalLinkPolicy::Open: return true; - case ExternalLinkPolicy::Ask: - return promptForExternalLink(requestUrl); + case ExternalLinkPolicy::Ask: { + // applyForceDarkMode() above ran against the external URL. If the user declines the + // navigation, restore the current qrc page's theme so it is not left inverted. + const bool accepted = promptForExternalLink(requestUrl); + if (!accepted) { + applyForceDarkMode(url()); + } + return accepted; + } case ExternalLinkPolicy::OpenInSystemBrowser: openInSystemBrowser(requestUrl); + applyForceDarkMode(url()); return false; } diff --git a/src/libs/browser/webpage.h b/src/libs/browser/webpage.h index 6c655b7d..40c19ade 100644 --- a/src/libs/browser/webpage.h +++ b/src/libs/browser/webpage.h @@ -27,6 +27,8 @@ protected: private: static bool promptForExternalLink(const QUrl &requestUrl); void applyBackgroundColor(); + void applyForceDarkMode(const QUrl &pageUrl); + void updateInternalPageTheme(); }; } // namespace Zeal::Browser diff --git a/src/libs/registry/docset.cpp b/src/libs/registry/docset.cpp index cd3a3b94..6f639744 100644 --- a/src/libs/registry/docset.cpp +++ b/src/libs/registry/docset.cpp @@ -38,7 +38,7 @@ constexpr auto IndexNameVersion = "0001"_L1; // Current index version constexpr auto DocumentsPath = "Contents/Resources/Documents/"_L1; -constexpr auto NotFoundPageUrl = "qrc:///browser/404.html"_L1; +constexpr auto NotFoundPageUrl = "qrc:///browser/not-found.html"_L1; namespace InfoPlist { constexpr auto CFBundleName = "CFBundleName"_L1; diff --git a/src/libs/ui/browsertab.cpp b/src/libs/ui/browsertab.cpp index 1f40ec5c..d96cb3e4 100644 --- a/src/libs/ui/browsertab.cpp +++ b/src/libs/ui/browsertab.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -30,7 +31,7 @@ namespace Zeal::WidgetUi { namespace { using Qt::Literals::StringLiterals::operator""_L1; -constexpr auto WelcomePageUrl = "qrc:///browser/welcome.html"_L1; +constexpr auto NewTabPageUrl = "qrc:///browser/new-tab.html"_L1; } // namespace BrowserTab::BrowserTab(QWidget *parent) @@ -226,7 +227,15 @@ SearchSidebar *BrowserTab::searchSidebar() const void BrowserTab::navigateToStartPage() { - m_webControl->load(QUrl(WelcomePageUrl)); + // These pages must follow Zeal's appearance, not the OS. Qt WebEngine resolves + // prefers-color-scheme from the OS for the first renderer (before the app scheme + // propagates), so the page cannot reliably detect the theme on its own. Carry the + // resolved theme in the URL fragment; the page applies it synchronously before its + // styles load (see new-tab.html). + QUrl url(NewTabPageUrl); + const bool dark = Core::Application::instance()->settings()->isDarkModeEnabled(); + url.setFragment(dark ? QStringLiteral("dark") : QStringLiteral("light")); + m_webControl->load(url); } void BrowserTab::search(const Registry::SearchQuery &query)