-
Activation Key
-
If you're finding that you're accidentally triggering Harper.
+
Activation Key
+
+ If you're finding that you're accidentally triggering Harper.
+
diff --git a/packages/chrome-plugin/src/popup/Main.svelte b/packages/chrome-plugin/src/popup/Main.svelte
index 76a5d38b..b85cbe6f 100644
--- a/packages/chrome-plugin/src/popup/Main.svelte
+++ b/packages/chrome-plugin/src/popup/Main.svelte
@@ -1,5 +1,5 @@
-
+
Harper
{#if popupState.page != "main"}
-
{/if}
@@ -48,9 +48,9 @@ function openSettings() {
{/if}
diff --git a/packages/chrome-plugin/src/popup/ReportProblematicLint.svelte b/packages/chrome-plugin/src/popup/ReportProblematicLint.svelte
index c6e15e57..7ff482fc 100644
--- a/packages/chrome-plugin/src/popup/ReportProblematicLint.svelte
+++ b/packages/chrome-plugin/src/popup/ReportProblematicLint.svelte
@@ -1,5 +1,5 @@
+
+{#if href}
+
+
+
+{:else}
+
+
+
+{/if}
diff --git a/packages/components/src/lib/Card.svelte b/packages/components/src/lib/Card.svelte
new file mode 100644
index 00000000..49fcfe2e
--- /dev/null
+++ b/packages/components/src/lib/Card.svelte
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/packages/components/src/lib/Collapsible.svelte b/packages/components/src/lib/Collapsible.svelte
new file mode 100644
index 00000000..f8b5ef89
--- /dev/null
+++ b/packages/components/src/lib/Collapsible.svelte
@@ -0,0 +1,17 @@
+
+
+
+ {title}
+
+
+
+
diff --git a/packages/components/src/lib/Input.svelte b/packages/components/src/lib/Input.svelte
new file mode 100644
index 00000000..127254d8
--- /dev/null
+++ b/packages/components/src/lib/Input.svelte
@@ -0,0 +1,35 @@
+
+
+
diff --git a/packages/components/src/lib/Link.svelte b/packages/components/src/lib/Link.svelte
new file mode 100644
index 00000000..ca57911c
--- /dev/null
+++ b/packages/components/src/lib/Link.svelte
@@ -0,0 +1,34 @@
+
+
+
+
+
diff --git a/packages/components/src/lib/Select.svelte b/packages/components/src/lib/Select.svelte
new file mode 100644
index 00000000..c2300d17
--- /dev/null
+++ b/packages/components/src/lib/Select.svelte
@@ -0,0 +1,45 @@
+
+
+
diff --git a/packages/components/src/lib/Textarea.svelte b/packages/components/src/lib/Textarea.svelte
new file mode 100644
index 00000000..c24b00b0
--- /dev/null
+++ b/packages/components/src/lib/Textarea.svelte
@@ -0,0 +1,22 @@
+
+
+
diff --git a/packages/components/src/lib/index.ts b/packages/components/src/lib/index.ts
new file mode 100644
index 00000000..3f90d0ed
--- /dev/null
+++ b/packages/components/src/lib/index.ts
@@ -0,0 +1,23 @@
+export {
+ Badge,
+ Checkbox,
+ Fileupload,
+ Label,
+ Radio,
+ Spinner,
+ Table,
+ TableBody,
+ TableBodyCell,
+ TableBodyRow,
+ TableHead,
+ TableHeadCell,
+ Toggle,
+} from 'flowbite-svelte';
+
+export { default as Button } from './Button.svelte';
+export { default as Card } from './Card.svelte';
+export { default as Collapsible } from './Collapsible.svelte';
+export { default as Input } from './Input.svelte';
+export { default as Link } from './Link.svelte';
+export { default as Select } from './Select.svelte';
+export { default as Textarea } from './Textarea.svelte';
diff --git a/packages/components/src/lib/styles.css b/packages/components/src/lib/styles.css
new file mode 100644
index 00000000..0b35f5a9
--- /dev/null
+++ b/packages/components/src/lib/styles.css
@@ -0,0 +1,74 @@
+@import "tailwindcss";
+
+@plugin "flowbite/plugin";
+
+@custom-variant dark (&:where(.dark, .dark *));
+
+@source "./src/lib/**/*.{svelte,ts}";
+@source "./node_modules/flowbite-svelte/**/*.{svelte,ts,js}";
+
+@theme {
+ --color-primary-50: #fef4e7; /* honey bronze */
+ --color-primary-100: #fce9cf;
+ --color-primary-200: #f9d49f;
+ --color-primary-300: #f7be6e;
+ --color-primary-400: #f4a83e;
+ --color-primary: #f1920e;
+ --color-primary-600: #c1750b;
+ --color-primary-700: #915808;
+ --color-primary-800: #603b06;
+ --color-primary-900: #301d03;
+ --color-primary-950: #221402;
+
+ --color-accent-50: #fee7e9; /* hot fuchsia */
+ --color-accent-100: #fccfd3;
+ --color-accent-200: #f99fa6;
+ --color-accent-300: #f76e7a;
+ --color-accent-400: #f43e4d;
+ --color-accent: #f10e21;
+ --color-accent-600: #c10b1a;
+ --color-accent-700: #910814;
+ --color-accent-800: #60060d;
+ --color-accent-900: #300307;
+ --color-accent-950: #220205;
+
+ --color-cream: #fef4e7; /* simple cream */
+ --color-cream-100: #fce9cf;
+ --color-cream-200: #f9d49f;
+ --color-cream-300: #f7be6e;
+ --color-cream-400: #f4a83e;
+ --color-cream-500: #f1920e;
+ --color-cream-600: #c1750b;
+ --color-cream-700: #915808;
+ --color-cream-800: #603b06;
+ --color-cream-900: #301d03;
+ --color-cream-950: #221402;
+
+ --color-champagne-mist-50: #fef4e7;
+ --color-champagne-mist-100: #fce9cf;
+ --color-champagne-mist-200: #fad49e;
+ --color-champagne-mist-300: #f7be6e;
+ --color-champagne-mist-400: #f5a83d;
+ --color-champagne-mist-500: #f2930d;
+ --color-champagne-mist-600: #c2750a;
+ --color-champagne-mist-700: #915808;
+ --color-champagne-mist-800: #613b05;
+ --color-champagne-mist-900: #301d03;
+ --color-champagne-mist-950: #221502;
+
+ --color-white: #fffdfa;
+ --color-white-100: #fceacf;
+ --color-white-200: #fad59e;
+ --color-white-300: #f7c06e;
+ --color-white-400: #f5ab3d;
+ --color-white-500: #f2960d;
+ --color-white-600: #c2780a;
+ --color-white-700: #915a08;
+ --color-white-800: #613c05;
+ --color-white-900: #301e03;
+ --color-white-950: #221502;
+}
+
+body {
+ @apply bg-white dark:bg-white-900 dark:text-white;
+}
diff --git a/packages/components/src/routes/+layout.svelte b/packages/components/src/routes/+layout.svelte
new file mode 100644
index 00000000..b4059508
--- /dev/null
+++ b/packages/components/src/routes/+layout.svelte
@@ -0,0 +1,7 @@
+
+
+{@render children()}
diff --git a/packages/components/src/routes/+page.svelte b/packages/components/src/routes/+page.svelte
new file mode 100644
index 00000000..da127aeb
--- /dev/null
+++ b/packages/components/src/routes/+page.svelte
@@ -0,0 +1,7 @@
+
+
+
Welcome to your library project
+
Create your package using @sveltejs/package and preview/showcase your work with SvelteKit
+
Visit svelte.dev/docs/kit to read the documentation
diff --git a/packages/components/src/routes/layout.css b/packages/components/src/routes/layout.css
new file mode 100644
index 00000000..f1d8c73c
--- /dev/null
+++ b/packages/components/src/routes/layout.css
@@ -0,0 +1 @@
+@import "tailwindcss";
diff --git a/packages/components/static/favicon.svg b/packages/components/static/favicon.svg
new file mode 100644
index 00000000..cc5dc66a
--- /dev/null
+++ b/packages/components/static/favicon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/components/svelte.config.js b/packages/components/svelte.config.js
new file mode 100644
index 00000000..3ad145a2
--- /dev/null
+++ b/packages/components/svelte.config.js
@@ -0,0 +1,18 @@
+import adapter from '@sveltejs/adapter-auto';
+import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
+
+/** @type {import('@sveltejs/kit').Config} */
+const config = {
+ // Consult https://svelte.dev/docs/kit/integrations
+ // for more information about preprocessors
+ preprocess: vitePreprocess(),
+
+ kit: {
+ // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
+ // If your environment is not supported, or you settled on a specific environment, switch out the adapter.
+ // See https://svelte.dev/docs/kit/adapters for more information about adapters.
+ adapter: adapter(),
+ },
+};
+
+export default config;
diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json
new file mode 100644
index 00000000..371777ed
--- /dev/null
+++ b/packages/components/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "extends": "./.svelte-kit/tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "allowJs": true,
+ "checkJs": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "sourceMap": true,
+ "strict": true,
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext"
+ }
+}
diff --git a/packages/components/vite.config.ts b/packages/components/vite.config.ts
new file mode 100644
index 00000000..da1f9251
--- /dev/null
+++ b/packages/components/vite.config.ts
@@ -0,0 +1,7 @@
+import { sveltekit } from '@sveltejs/kit/vite';
+import tailwindcss from '@tailwindcss/vite';
+import { defineConfig } from 'vite';
+
+export default defineConfig({
+ plugins: [tailwindcss(), sveltekit()],
+});
diff --git a/packages/lint-framework/package.json b/packages/lint-framework/package.json
index 64f72de1..26e7b072 100644
--- a/packages/lint-framework/package.json
+++ b/packages/lint-framework/package.json
@@ -24,6 +24,7 @@
"@fortawesome/fontawesome-svg-core": "^7.1.0",
"@fortawesome/free-solid-svg-icons": "^7.1.0",
"bowser": "^2.12.1",
+ "colorjs.io": "^0.5.2",
"virtual-dom": "^2.1.1"
},
"peerDependencies": {
diff --git a/packages/lint-framework/src/index.ts b/packages/lint-framework/src/index.ts
index a4f1e2b5..5356c178 100644
--- a/packages/lint-framework/src/index.ts
+++ b/packages/lint-framework/src/index.ts
@@ -5,7 +5,6 @@ export * from './lint/editorUtils';
export { default as Highlights } from './lint/Highlights';
export { default as LintFramework } from './lint/LintFramework';
export * from './lint/lintKindColor';
-export { default as lintKindColor } from './lint/lintKindColor';
export { default as PopupHandler } from './lint/PopupHandler';
export { default as RenderBox } from './lint/RenderBox';
export * from './lint/unpackLint';
diff --git a/packages/lint-framework/src/lint/Highlights.ts b/packages/lint-framework/src/lint/Highlights.ts
index 13933d2b..e7373d8b 100644
--- a/packages/lint-framework/src/lint/Highlights.ts
+++ b/packages/lint-framework/src/lint/Highlights.ts
@@ -17,7 +17,7 @@ import {
getSlateRoot,
getTrixRoot,
} from './editorUtils';
-import lintKindColor, { type LintKind } from './lintKindColor';
+import { type LintKind, lintKindColor } from './lintKindColor';
import RenderBox from './RenderBox';
import type SourceElement from './SourceElement';
import type { UnpackedLint } from './unpackLint';
diff --git a/packages/lint-framework/src/lint/SuggestionBox.ts b/packages/lint-framework/src/lint/SuggestionBox.ts
index adf73ed7..e46f5013 100644
--- a/packages/lint-framework/src/lint/SuggestionBox.ts
+++ b/packages/lint-framework/src/lint/SuggestionBox.ts
@@ -5,7 +5,7 @@ import type { VNode } from 'virtual-dom';
import h from 'virtual-dom/h';
import bookDownSvg from '../assets/bookDownSvg';
import type { IgnorableLintBox, LintBox } from './Box';
-import lintKindColor from './lintKindColor';
+import { type LintKind, lintKindColor, lintKindTextColor } from './lintKindColor';
// Decoupled: actions passed in by framework consumer
import type { UnpackedLint, UnpackedSuggestion } from './unpackLint';
@@ -190,6 +190,7 @@ function addToDictionary(
}
function suggestions(
+ lintKind: LintKind,
suggestions: UnpackedSuggestion[],
apply: (s: UnpackedSuggestion) => void,
): any {
@@ -197,7 +198,13 @@ function suggestions(
const label = s.replacement_text !== '' ? s.replacement_text : String(s.kind);
const desc = `Replace with "${label}"`;
const props = i === 0 ? { hook: new FocusHook() } : {};
- return button(label, { background: '#2DA44E', color: '#FFFFFF' }, () => apply(s), desc, props);
+ return button(
+ label,
+ { background: lintKindColor(lintKind), color: lintKindTextColor(lintKind) },
+ () => apply(s),
+ desc,
+ props,
+ );
});
}
@@ -221,10 +228,10 @@ function reportProblemButton(reportError?: () => Promise
): any {
);
}
-function styleTag() {
+function styleTag(lintKind: LintKind) {
return h('style', { id: 'harper-suggestion-style' }, [
`code{
- background-color:#e3eccf;
+ text-decoration: underline solid ${lintKindColor(lintKind)} 2px;
padding:0.125rem;
border-radius:0.25rem
}
@@ -351,10 +358,16 @@ function styleTag() {
animation: fadeIn 100ms ease-in-out forwards;
}
- @keyframes fadeIn {
- from { opacity: 0; }
- to { opacity: 1; }
- }
+ @keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: scale(0.95);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+ }
@media (prefers-color-scheme:dark){
code{background-color:#1f2d3d;color:#c9d1d9}
@@ -437,6 +450,7 @@ export default function SuggestionBox(
top: bottom ? '' : `${top}px`,
bottom: bottom ? `${bottom}px` : '',
left: `${left}px`,
+ transformOrigin: `${bottom ? 'bottom' : 'top'} left`,
};
return h(
@@ -447,7 +461,7 @@ export default function SuggestionBox(
'harper-close-on-escape': new CloseOnEscapeHook(close),
},
[
- styleTag(),
+ styleTag(box.lint.lint_kind),
header(
box.lint.lint_kind_pretty,
lintKindColor(box.lint.lint_kind),
@@ -458,7 +472,7 @@ export default function SuggestionBox(
),
body(box.lint.message_html),
footer(
- suggestions(box.lint.suggestions, (v) => {
+ suggestions(box.lint.lint_kind, box.lint.suggestions, (v) => {
box.applySuggestion(v);
close();
}),
diff --git a/packages/lint-framework/src/lint/lintKindColor.ts b/packages/lint-framework/src/lint/lintKindColor.ts
index 2c0cbda6..49c64749 100644
--- a/packages/lint-framework/src/lint/lintKindColor.ts
+++ b/packages/lint-framework/src/lint/lintKindColor.ts
@@ -1,3 +1,5 @@
+import { getContrastingTextColor } from './utils';
+
// First, define the color map as a constant
const LINT_KIND_COLORS = {
Agreement: '#228B22', // Forest green
@@ -29,10 +31,15 @@ export type LintKind = keyof typeof LINT_KIND_COLORS;
export const LINT_KINDS = Object.keys(LINT_KIND_COLORS) as LintKind[];
// The main function that uses the map
-export default function lintKindColor(lintKindKey: string): string {
+export function lintKindColor(lintKindKey: string): string {
const color = LINT_KIND_COLORS[lintKindKey as LintKind];
if (!color) {
throw new Error(`Unexpected lint kind: ${lintKindKey}`);
}
return color;
}
+
+export function lintKindTextColor(lintKindKeyOrColor: string): 'black' | 'white' {
+ const color = LINT_KIND_COLORS[lintKindKeyOrColor as LintKind] ?? lintKindKeyOrColor;
+ return getContrastingTextColor(color);
+}
diff --git a/packages/lint-framework/src/lint/utils.ts b/packages/lint-framework/src/lint/utils.ts
new file mode 100644
index 00000000..9d73e511
--- /dev/null
+++ b/packages/lint-framework/src/lint/utils.ts
@@ -0,0 +1,13 @@
+import Color from 'colorjs.io';
+
+/** Get the text color that best contrasts with a background of the provided color. */
+export function getContrastingTextColor(color: string): 'black' | 'white' {
+ const c = new Color(color);
+ const luminance = c.luminance;
+
+ if (luminance > 0.5) {
+ return 'black';
+ } else {
+ return 'white';
+ }
+}
diff --git a/packages/web/package.json b/packages/web/package.json
index 12fd0c4b..f8934350 100644
--- a/packages/web/package.json
+++ b/packages/web/package.json
@@ -19,7 +19,6 @@
"autoprefixer": "^10.4.21",
"drizzle-kit": "^0.31.5",
"flowbite": "^3.1.2",
- "flowbite-svelte": "^0.44.18",
"svelte": "^5.15.0",
"svelte-check": "^4.1.5",
"tailwindcss": "^4.1.16",
@@ -35,6 +34,7 @@
"@sveltepress/theme-default": "^5.0.7",
"@sveltepress/vite": "^1.1.5",
"chart.js": "^4.4.8",
+ "components": "workspace:*",
"drizzle-orm": "^0.44.6",
"drizzle-zod": "^0.8.3",
"harper.js": "workspace:*",
diff --git a/packages/web/src/app.css b/packages/web/src/app.css
index dd148a09..3370b8f0 100644
--- a/packages/web/src/app.css
+++ b/packages/web/src/app.css
@@ -1,53 +1,118 @@
@import "tailwindcss";
+@import "components/components.css";
-@layer base {
- body {
- @apply bg-white text-black dark:bg-gray-900 dark:text-white;
- }
+@custom-variant dark (&:where(.dark, .dark *));
- ul {
- @apply list-disc pl-4;
- }
+@theme {
+ --color-primary-50: #fef4e7; /* honey bronze */
+ --color-primary-100: #fce9cf;
+ --color-primary-200: #f9d49f;
+ --color-primary-300: #f7be6e;
+ --color-primary-400: #f4a83e;
+ --color-primary: #f1920e;
+ --color-primary-600: #c1750b;
+ --color-primary-700: #915808;
+ --color-primary-800: #603b06;
+ --color-primary-900: #301d03;
+ --color-primary-950: #221402;
- ol {
- @apply list-decimal pl-4;
- }
+ --color-accent-50: #fee7e9; /* hot fuchsia */
+ --color-accent-100: #fccfd3;
+ --color-accent-200: #f99fa6;
+ --color-accent-300: #f76e7a;
+ --color-accent-400: #f43e4d;
+ --color-accent: #f10e21;
+ --color-accent-600: #c10b1a;
+ --color-accent-700: #910814;
+ --color-accent-800: #60060d;
+ --color-accent-900: #300307;
+ --color-accent-950: #220205;
- h1 {
- @apply text-4xl font-extrabold tracking-tight lg:text-5xl py-4;
- }
+ --color-cream: #fef4e7; /* simple cream */
+ --color-cream-100: #fce9cf;
+ --color-cream-200: #f9d49f;
+ --color-cream-300: #f7be6e;
+ --color-cream-400: #f4a83e;
+ --color-cream-500: #f1920e;
+ --color-cream-600: #c1750b;
+ --color-cream-700: #915808;
+ --color-cream-800: #603b06;
+ --color-cream-900: #301d03;
+ --color-cream-950: #221402;
- h2 {
- @apply text-3xl font-semibold tracking-tight py-4;
- }
+ --color-champagne-mist-50: #fef4e7;
+ --color-champagne-mist-100: #fce9cf;
+ --color-champagne-mist-200: #fad49e;
+ --color-champagne-mist-300: #f7be6e;
+ --color-champagne-mist-400: #f5a83d;
+ --color-champagne-mist-500: #f2930d;
+ --color-champagne-mist-600: #c2750a;
+ --color-champagne-mist-700: #915808;
+ --color-champagne-mist-800: #613b05;
+ --color-champagne-mist-900: #301d03;
+ --color-champagne-mist-950: #221502;
- h3 {
- @apply text-2xl font-semibold tracking-tight py-4;
- }
-
- h4 {
- @apply text-xl font-semibold tracking-tight;
- }
-
- p {
- @apply leading-7 [&:not(:first-child)]:mt-6;
- }
-
- a {
- @apply underline-offset-4 underline;
- }
-
- blockquote {
- @apply mt-6 border-l-2 border-gray-200 pl-6 italic dark:border-gray-700;
- }
+ --color-white: #fffdfa;
+ --color-white-100: #fceacf;
+ --color-white-200: #fad59e;
+ --color-white-300: #f7c06e;
+ --color-white-400: #f5ab3d;
+ --color-white-500: #f2960d;
+ --color-white-600: #c2780a;
+ --color-white-700: #915a08;
+ --color-white-800: #613c05;
+ --color-white-900: #301e03;
+ --color-white-950: #221502;
}
-* {
+body {
+ @apply bg-white text-black dark:bg-black dark:text-white;
+
font-family:
Atkinson Hyperlegible,
sans-serif;
}
+ul {
+ @apply list-disc pl-4;
+}
+
+ol {
+ @apply list-decimal pl-4;
+}
+
+h1 {
+ @apply text-4xl font-extrabold tracking-tight lg:text-5xl py-4;
+ font-family: Domine, serif;
+}
+
+h2 {
+ @apply text-3xl font-semibold tracking-tight py-4;
+ font-family: Domine, serif;
+}
+
+h3 {
+ @apply text-2xl font-semibold tracking-tight py-4;
+ font-family: Domine, serif;
+}
+
+h4 {
+ @apply text-xl font-semibold tracking-tight;
+ font-family: Domine, serif;
+}
+
+p {
+ @apply leading-7 [&:not(:first-child)]:mt-6;
+}
+
+a {
+ @apply underline-offset-4 text-black dark:text-white;
+}
+
+blockquote {
+ @apply mt-6 border-l-2 border-gray-200 pl-6 italic dark:border-gray-700;
+}
+
code {
font-family: "JetBrains Mono", monospace;
word-break: keep-all;
diff --git a/packages/web/src/app.html b/packages/web/src/app.html
index 58bc9add..dcff643f 100644
--- a/packages/web/src/app.html
+++ b/packages/web/src/app.html
@@ -70,10 +70,22 @@
+
+
+
+
+
+
%sveltekit.head%
-
+
%sveltekit.body%
diff --git a/packages/web/src/lib/components/DefaultNeovimConfig.svelte b/packages/web/src/lib/components/DefaultNeovimConfig.svelte
index 758f6105..4a59cf17 100644
--- a/packages/web/src/lib/components/DefaultNeovimConfig.svelte
+++ b/packages/web/src/lib/components/DefaultNeovimConfig.svelte
@@ -1,5 +1,5 @@
-
-
+
+
{@html content.replace(/\n\n/g, '
')}
diff --git a/packages/web/src/lib/components/LazyEditor.svelte b/packages/web/src/lib/components/LazyEditor.svelte
index 00f53cc4..c5735d29 100644
--- a/packages/web/src/lib/components/LazyEditor.svelte
+++ b/packages/web/src/lib/components/LazyEditor.svelte
@@ -1,5 +1,5 @@
-
+
Problems
-
{allOpen ? 'Collapse all' : 'Open all'}
-
-
+
Ignore all
-
+
diff --git a/packages/web/src/lib/components/Testimonial.svelte b/packages/web/src/lib/components/Testimonial.svelte
index 8fe9fb7b..fd412be5 100644
--- a/packages/web/src/lib/components/Testimonial.svelte
+++ b/packages/web/src/lib/components/Testimonial.svelte
@@ -1,10 +1,12 @@
-
+
{testimonial}
@@ -12,4 +14,4 @@ export let testimonial: string;
{authorName}
{authorSubtitle}
-
+
diff --git a/packages/web/src/lib/components/TestimonialCollection.svelte b/packages/web/src/lib/components/TestimonialCollection.svelte
index 33bab05c..650b73a8 100644
--- a/packages/web/src/lib/components/TestimonialCollection.svelte
+++ b/packages/web/src/lib/components/TestimonialCollection.svelte
@@ -1,4 +1,5 @@
-
-
-
-
@@ -39,12 +30,12 @@ let displayName = names[Math.floor(Math.random() * names.length)];
diff --git a/packages/web/src/routes/+page.svelte b/packages/web/src/routes/+page.svelte
index 3a6c6be5..494dbdb1 100644
--- a/packages/web/src/routes/+page.svelte
+++ b/packages/web/src/routes/+page.svelte
@@ -22,8 +22,8 @@ import SublimeLogo from '$lib/components/SublimeLogo.svelte';
import WordPressLogo from '$lib/components/WordPressLogo.svelte';
import ZedLogo from '$lib/components/ZedLogo.svelte';
import EdgeLogo from '$lib/components/EdgeLogo.svelte';
+import { Card, Collapsible, Link } from 'components';
import { browser } from '$app/environment';
- import Testimonial from '$lib/components/Testimonial.svelte';
/**
* @param {string} keyword
@@ -104,56 +104,56 @@ const testimonials = [
Hi. I'm Harper.
- The Free Grammar Checker That Respects Your Privacy
+ The Free Grammar Checker That Respects Your Privacy
-
GitHub
-
+
{#if agentHas("firefox")}
-
Add to Firefox*]:m-2 skew-hover"
+ >
Add to Firefox
{:else if agentHas("Edg")}
-
Add to Edge*]:m-2 skew-hover-left"
+ >
Add to Edge
{:else}
-
Add to Chrome*]:m-2 skew-hover"
+ >
Add to Chrome
{/if}
-
Install in VS Code
-
-
+
Install in Obsidian
-
-
+
AuthorAuthor
-
+
{#if browser}
{/if}
@@ -193,92 +193,112 @@ const testimonials = [
Native Everywhere
Harper is available as a
- language server,
- JavaScript library
+ language server,
+ JavaScript library
through WebAssembly, and
- Rust crate,
+ Rust crate,
so you can get fantastic grammar checking anywhere you work.
That said, we take extra care to make sure the
- Visual Studio Code,
- Neovim,
- Obsidian, and
- Chrome
+ Visual Studio Code,
+ Neovim,
+ Obsidian, and
+ Chrome
extensions are amazing.
-
@@ -290,9 +310,9 @@ const testimonials = [
No network request, no massive language models, no fuss.
-
+
-
+
@@ -304,119 +324,89 @@ const testimonials = [
FAQs
-
-
- Is Harper Free?
-
-
+
+
Yes. Harper is free in every sense of the word. You don't need a credit card to start using
Harper, and the source code is freely available under the Apache-2.0 license.
-
-
-
- How Does Harper Work?
-
-
+
+
+
Harper watches your writing and provides instant suggestions when it notices a grammatical
error. When you see an underline, it's probably because Harper has something to say.
-
-
-
- Does Harper Change The Meaning of My Words?
-
-
+
+
+
No. Harper will never intentionally suggest an edit that might change your meaning. Harper
strives to never make it harder to express your creativity.
-
-
-
- Is Harper Really Private?
-
-
+
+
+
Harper is the only widespread and comprehensive grammar checker that is truly private. Your
data never leaves your device. Your writing should remain just that: yours.
-
-
-
- How Do I Use or Integrate Harper?
-
-
+
+
+
That depends on your use case. Do you want to use it within Obsidian? We have an
- Obsidian plugin. Do you want to use it within WordPress? We have a
- WordPress plugin. Do you want to use it within your Browser? We have a
- Chrome extension and a
- Firefox plugin. Do you want to use it within your code editor? We have documentation on how you can integrate with
- Visual Studio Code and its forks,
- Neovim,
- Helix,
- Emacs,
- Zed and
- Sublime Text. If you're using a different code editor, then you can integrate directly with our language server,
- harper-ls. Do you want to integrate it in your web app or your JavaScript/TypeScript codebase? You can use
- harper.js. Do you want to integrate it in your Rust program or codebase? You can use
- harper-core.
+ Obsidian plugin. Do you want to use it within WordPress? We have a
+ WordPress plugin. Do you want to use it within your Browser? We have a
+ Chrome extension and a
+ Firefox plugin. Do you want to use it within your code editor? We have documentation on how you can integrate with
+ Visual Studio Code and its forks,
+ Neovim,
+ Helix,
+ Emacs,
+ Zed and
+ Sublime Text. If you're using a different code editor, then you can integrate directly with our language server,
+ harper-ls. Do you want to integrate it in your web app or your JavaScript/TypeScript codebase? You can use
+ harper.js. Do you want to integrate it in your Rust program or codebase? You can use
+ harper-core.
-
-
-
- What Human Languages Do You Support?
-
-
+
+
+
We currently only support English and its dialects British, American, Canadian, and
Australian. Other languages are on the horizon, but we want our English support to be truly
amazing before we diversify.
-
-
-
- What Programming Languages Do You Support?
-
-
- For harper-ls and our code editor integrations, we support a wide variety of
- programming languages. You can view all of them over at the
- harper-ls documentation.
- We are entirely open to PRs that add support. If you just want to be able to run grammar checking
- on your code's comments, you can use
- this PR as a model for what to do.
-
-
- For harper.js and those that use it under the hood like our Obsidian plugin, we
- support plaintext and/or Markdown.
-
-
-
-
- Where Did the Name Harper Come From?
-
-
- See this blog post.
-
-
-
-
- Do I Need a GPU?
-
-
-
No. Harper runs on-device, no matter what. There are no special hardware requirements. No GPU, no additional memory, no fuss.
+
+
+
+
+ For harper-ls and our code editor integrations, we support a wide variety of
+ programming languages. You can view all of them over at the
+ harper-ls documentation.
+ We are entirely open to PRs that add support. If you just want to be able to run grammar checking
+ on your code's comments, you can use
+ this PR as a model for what to do.
+
+
+ For harper.js and those that use it under the hood like our Obsidian plugin, we
+ support plaintext and/or Markdown.
+
-
-
-
- What Do I Do If My Question Isn't Here?
-
-
- You can join our
- Discord
- and ask your questions there or you can start a discussion over at
- GitHub.
+
+
+
+ See this blog post.
-
+
+
+ No. Harper runs on-device, no matter what. There are no special hardware requirements. No GPU, no additional memory, no fuss.
+
+
+
+ You can join our
+ Discord
+ and ask your questions there or you can start a discussion over at
+ GitHub.
+
+
@@ -425,7 +415,7 @@ const testimonials = [
Harper is completely open source under the Apache-2.0 license.
Come pay us a visit on
- GitHub.
+ GitHub.
diff --git a/packages/web/src/routes/docs/integrations/wordpress/+page.md b/packages/web/src/routes/docs/integrations/wordpress/+page.md
index f0b5aa9e..ba0a847f 100644
--- a/packages/web/src/routes/docs/integrations/wordpress/+page.md
+++ b/packages/web/src/routes/docs/integrations/wordpress/+page.md
@@ -3,7 +3,7 @@ title: Harper for WordPress
---
Harper still works great with WordPress, but the recommended way to use it today is the
diff --git a/packages/web/src/routes/docs/rules/+page.svelte b/packages/web/src/routes/docs/rules/+page.svelte
index 7a2446dd..03a2df3a 100644
--- a/packages/web/src/routes/docs/rules/+page.svelte
+++ b/packages/web/src/routes/docs/rules/+page.svelte
@@ -6,7 +6,7 @@ import {
TableBodyRow,
TableHead,
TableHeadCell,
-} from 'flowbite-svelte';
+} from 'components';
import { binary, type LintConfig, LocalLinter } from 'harper.js';
export const frontmatter = {
diff --git a/packages/web/src/routes/install-browser-extension/+page.svelte b/packages/web/src/routes/install-browser-extension/+page.svelte
index b6da0efb..1b1ae431 100644
--- a/packages/web/src/routes/install-browser-extension/+page.svelte
+++ b/packages/web/src/routes/install-browser-extension/+page.svelte
@@ -1,5 +1,5 @@
diff --git a/packages/web/src/routes/stats/+page.svelte b/packages/web/src/routes/stats/+page.svelte
index 550290ca..1c99968a 100644
--- a/packages/web/src/routes/stats/+page.svelte
+++ b/packages/web/src/routes/stats/+page.svelte
@@ -7,7 +7,7 @@ import {
TableBodyRow,
TableHead,
TableHeadCell,
-} from 'flowbite-svelte';
+} from 'components';
import { binary, type Summary, WorkerLinter } from 'harper.js';
import LintKindChart from '$lib/components/LintKindChart.svelte';
diff --git a/packages/web/src/routes/titlecase/+page.svelte b/packages/web/src/routes/titlecase/+page.svelte
index a7653ff9..008063ea 100644
--- a/packages/web/src/routes/titlecase/+page.svelte
+++ b/packages/web/src/routes/titlecase/+page.svelte
@@ -1,5 +1,5 @@