From 969df9a07fc4e36c4dc65d6358703f02894eec32 Mon Sep 17 00:00:00 2001 From: Thomas Lin Pedersen Date: Sun, 15 Mar 2026 16:49:54 +0100 Subject: [PATCH 1/2] improvements to homepage --- doc/assets/visualize.svg | 35 ++++ doc/index.qmd | 187 +++++++++++++++-- doc/styles.scss | 439 ++++++++++++++++++++++++++++----------- 3 files changed, 524 insertions(+), 137 deletions(-) create mode 100644 doc/assets/visualize.svg diff --git a/doc/assets/visualize.svg b/doc/assets/visualize.svg new file mode 100644 index 00000000..72e18bdc --- /dev/null +++ b/doc/assets/visualize.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/index.qmd b/doc/index.qmd index 8ee87c4b..a0b02066 100644 --- a/doc/index.qmd +++ b/doc/index.qmd @@ -5,22 +5,170 @@ section-divs: false toc: false lightbox: false repo-actions: false +listing: + - id: example-carousel + contents: gallery/examples + type: grid + grid-columns: 6 + image-height: 150px + fields: [image, title] + sort: "order" + max-items: 6 +include-in-header: + text: | + --- ::: {.hero-banner} -![](assets/hero.png){.hero-image} -::: {.hero-content} -# SQL meets Grammar of Graphics +# QUERY ![](assets/visualize.svg){.hviz} UNDERSTAND -A declarative visualization language that extends SQL with powerful data visualization capabilities. +::: {.hero-body} +ggsql brings the elegance of the Grammar of Graphics to SQL. Write familiar queries, add visualization clauses, and see your data transform into beautiful, composable charts — no context switching, no separate tools, just SQL with superpowers. ::: {.hero-buttons} -[Get Started](get_started.qmd){.btn .btn-secondary .btn-lg} -[View Examples](gallery/index.qmd){.btn .btn-outline-light .btn-lg} +[Get Started](get_started.qmd){.btn .btn-primary .btn-lg} +[View Examples](gallery/index.qmd){.btn .btn-outline-primary .btn-lg} ::: +::: + +::: + +::: {.content-block .dark-bg} +### Try it out + +ggsql runs in the browser as well! Edit the code and watch the plot update. + +```{ggsql} +-- Regular query +SELECT * FROM ggsql:penguins +WHERE island = 'Biscoe' +-- Followed by visualization declaration +VISUALISE bill_len AS x, bill_dep AS y, body_mass AS fill +DRAW point +DRAW linear + MAPPING 0.4 AS coef, -1 AS intercept +SCALE BINNED fill +LABEL + title => 'Relationship between bill dimensions in 3 species of penguins', + x => 'Bill length (mm)', + y => 'Bill depth (mm)' +``` + +::: + +::: {.content-block .examples-carousel} +## Explore the examples +::: {#example-carousel} ::: + +[See all examples →](gallery/index.qmd){.see-all-link} + +::: + +::: {.content-block .dark-bg .install-section} +### Install it today + +::: {#installer-buttons} + +::: + +::: {.install-cli-options} +**Or via command line:** +`uv pip install ggsql-jupyter && ggsql-jupyter --install` +· +`cargo install ggsql` +::: + +```{=html} + +``` ::: ::: {.content-block} @@ -65,35 +213,41 @@ You also avoid needing agents to launch full programming languages like Python o ::: ::: {.feature} -### There where you need it +### Connect directly to your data + +ggsql interfaces directly with your database. Want to create a histogram over 1 billion observations? No problem! All calculations are pushed to the database so you only extract what is needed for the visual. +::: -ggsql is available where you do your data analysis: +::: + +::: + +::: {.content-block .integrations} + +## Available where your data is ::: {.tool-grid} ::: {.tool-item} ![](assets/logos/positron.svg){.tool-logo} -Positron +[Positron](https://positron.posit.co/) ::: ::: {.tool-item} ![](assets/logos/quarto.svg){.tool-logo} -Quarto +[Quarto](https://quarto.org/) ::: ::: {.tool-item} ![](assets/logos/jupyter.svg){.tool-logo} -Jupyter +[Jupyter](https://jupyter.org) ::: ::: {.tool-item} ![](assets/logos/vscode.svg){.tool-logo} -VS Code +[VS Code](https://code.visualstudio.com/) ::: -::: -::: - ::: ::: -::: {.content-block .alt-bg} +::: {.content-block} ::: {.cta-section} @@ -112,3 +266,4 @@ Or try our [online playground](wasm/) to experience the syntax _right now_. ::: ::: + diff --git a/doc/styles.scss b/doc/styles.scss index 31bad831..9534ffe0 100644 --- a/doc/styles.scss +++ b/doc/styles.scss @@ -31,134 +31,131 @@ code { margin-bottom: 1.5rem; } +// Front page paleteal background with subtle grid +body:has(.hero-banner) { + background-color: var(--brand-paleteal, #DEF1EB); + background-image: + linear-gradient(rgba(0, 95, 115, 0.06) 1px, transparent 1px), + linear-gradient(90deg, rgba(0, 95, 115, 0.06) 1px, transparent 1px); + background-size: 40px 40px; + background-attachment: fixed; +} + .hero-banner { - padding: 0; - margin: 0; width: 100%; - display: flex; - flex-direction: column; - align-items: center; - background: var(--brand-paleteal, #DEF1EB); + padding: 4rem 2rem; + background: transparent; - &>p { - margin: 0; - } - .hero-image { - width: 100%; - max-width: 1200px; - height: auto; - object-fit: contain; - } - .hero-content { - width: 100%; - padding: 2rem; - background: var(--brand-darkteal); - text-align: center; + h1, .hero-body { + max-width: 900px; + margin-left: auto; + margin-right: auto; - h1 { - font-size: 2.5rem; - font-weight: 600; - color: var(--brand-lightteal); - margin-bottom: 0.5rem; + .hcode { + font-family: "Fira Code"; + font-weight: 100; } - p { - font-size: 1.25rem; - color: var(--brand-white); - max-width: 600px; - margin: 0 auto 1.5rem; + .hviz { + display: inline; + height: 0.85em; + width: auto; + vertical-align: baseline; + margin: 0 0.1em; + position: relative; + top: 0.08em; + } + + .hund { + font-family: "Antic Didone", serif; + font-weight: 400; + font-style: normal; } } - .hero-buttons { + h1 { + font-size: 3rem; + font-weight: 700; + color: var(--brand-darkteal, #005F73); + margin-bottom: 1.5rem; + line-height: 1.2; + letter-spacing: -0.02em; + text-align: left; + } + + .hero-body { display: flex; - gap: 1rem; - justify-content: center; - flex-wrap: wrap; + flex-direction: column; + gap: 2rem; + align-items: center; } + .hero-body > p { + font-size: 1.25rem; + color: var(--brand-black, #001219); + line-height: 1.7; + margin: 0; + text-align: center; + } - // Wide layout: content box to the right of image - @media (min-width: 1200px) { - flex-direction: row; - align-items: center; - justify-content: flex-end; - min-height: 400px; - overflow: hidden; + .hero-buttons { + display: flex; + flex-direction: column; + gap: 1rem; + width: fit-content; - // Constrain total width to match content-blocks - &>p, .hero-content { - max-width: 1200px; + > p { + display: contents; } - &>p { - flex: 1 1 auto; - display: flex; - justify-content: flex-end; - align-items: center; - margin: 0; + .btn { + text-align: center; + display: block; } + } - .hero-image { - max-width: 100%; - max-height: 100%; - object-fit: contain; - object-position: center; - mask-image: linear-gradient( - to right, - transparent 0%, - black 15%, - black 85%, - transparent 100% - ); - -webkit-mask-image: linear-gradient( - to right, - transparent 0%, - black 15%, - black 85%, - transparent 100% - ); - } - - .hero-content { - flex: 0 0 auto; - width: auto; - max-width: 400px; - padding: 0rem 1rem 1rem; - margin: 4rem calc((100vw - 1200px) / 2) 2rem -80px; - border-radius: 12px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); - position: relative; - z-index: 1; + @media (min-width: 768px) { + padding: 5rem 2rem; - h1 { - font-size: 1.5rem; - } + h1 { + font-size: 3.5rem; + } - p { - font-size: 1rem; - margin-bottom: 1rem; - } + .hero-body { + flex-direction: row; + justify-content: space-between; + align-items: flex-start; + gap: 3rem; } - .hero-buttons { - gap: 0.75rem; + .hero-body > p { + font-size: 1.35rem; + text-align: left; + flex: 1; + } - .btn.btn-lg { - padding: 0.6rem 1.25rem; - font-size: 0.95rem; - } + .hero-buttons { + flex-shrink: 0; + align-items: stretch; } } } .content-block { - max-width: 100%; - margin: 0 auto; - padding: 4rem 2rem; - background: var(--brand-darkteal); - color: var(--brand-paleteal); + max-width: 1200px; + margin: 2rem auto; + padding: 4rem 2rem 2rem; + background: transparent; + color: var(--brand-darkteal); + border-radius: 24px; + + @media (max-width: 1232px) { + border-radius: 0; + margin-left: 0; + margin-right: 0; + max-width: 100%; + } > * { max-width: 1200px; @@ -167,28 +164,32 @@ code { } > h2 { - text-align: center; + text-align: left; font-size: 2rem; - color: var(--brand-paleteal); + color: var(--brand-darkteal); margin-bottom: 3rem; - border-bottom-color: var(--brand-paleteal); + border-bottom-color: var(--brand-darkteal); } a { - color: var(--brand-lightteal); + color: var(--brand-darkteal); } - &.alt-bg { - background: var(--brand-paleteal, #DEF1EB); + &.dark-bg { + background: rgba(249, 249, 249, 0.7); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); color: var(--brand-darkteal); + box-shadow: 0 8px 32px rgba(0, 95, 115, 0.1); + border: 1px solid rgba(249, 249, 249, 0.5); - > section > p > a { - color: black + > h2, > h3 { + color: var(--brand-darkteal); + border-bottom-color: var(--brand-darkteal); } - > h2 { + a:not(.btn) { color: var(--brand-darkteal); - border-bottom-color: var(--brand-darkteal); } } } @@ -211,11 +212,19 @@ code { grid-template-rows: auto 1fr; column-gap: 2rem; row-gap: 0.75rem; - background: white; - border-radius: 8px; + background: rgba(255, 255, 255, 0.85); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border-radius: 16px; padding: 2rem; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); - border: 1px solid var(--brand-lightteal, #94D2BD); + box-shadow: 0 8px 32px rgba(0, 95, 115, 0.1); + border: 1px solid rgba(148, 210, 189, 0.4); + transition: transform 0.3s ease, box-shadow 0.3s ease; + + &:hover { + transform: translateY(-4px); + box-shadow: 0 12px 40px rgba(0, 95, 115, 0.15); + } h3 { grid-column: 1; @@ -321,15 +330,25 @@ code { .tool-grid { display: grid; grid-template-columns: repeat(2, 1fr); - gap: 1rem; - margin-top: 1rem; + gap: 1.5rem; + margin-top: 1.5rem; + justify-items: center; - // Single row when box is wide - @media (min-width: 769px) and (max-width: 999px) { + @media (min-width: 600px) { grid-template-columns: repeat(4, 1fr); } } +.integrations { + text-align: center; + + > p { + max-width: 600px; + margin-left: auto; + margin-right: auto; + } +} + .tool-item { display: flex; flex-direction: column; @@ -343,6 +362,15 @@ code { > p { margin: 0; } + + a { + text-decoration: none; + color: inherit; + + &:hover { + text-decoration: none; + } + } } .tool-logo { @@ -351,6 +379,81 @@ code { object-fit: contain; } +// Install section +.install-section { + text-align: center; + + .install-grid { + display: grid; + grid-template-columns: 1fr; + gap: 2rem; + margin-top: 1.5rem; + + @media (min-width: 600px) { + grid-template-columns: 1fr 1fr; + gap: 3rem; + } + } + + .install-column { + h4 { + font-size: 1.1rem; + margin-bottom: 1rem; + opacity: 0.9; + } + + pre { + text-align: left; + font-size: 0.9rem; + } + } + + #installer-buttons { + .install-version { + margin-bottom: 1.5rem; + font-size: 0.95rem; + opacity: 0.8; + } + + .install-buttons-row { + display: flex; + gap: 2rem; + justify-content: center; + flex-wrap: wrap; + } + + .install-option { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + } + + .install-btn { + min-width: 160px; + } + + .install-description { + font-size: 0.85rem; + opacity: 0.7; + margin: 0; + } + } + + .install-cli-options { + margin-top: 1.5rem; + font-size: 0.9rem; + opacity: 0.85; + + code { + background: rgba(0, 95, 115, 0.08); + padding: 0.2em 0.5em; + border-radius: 4px; + font-size: 0.85em; + } + } +} + .cta-section { text-align: center; padding: 2rem 0; @@ -373,11 +476,91 @@ code { } } -// Hide category badges on gallery example pages -.quarto-listing-category { - // Keep category sidebar for filtering +// Example carousel +.examples-carousel { + #listing-example-carousel { + overflow-x: auto; + scroll-snap-type: x mandatory; + -webkit-overflow-scrolling: touch; + padding-bottom: 1rem; + mask-image: linear-gradient( + to right, + transparent 0%, + black 2%, + black 90%, + transparent 100% + ); + -webkit-mask-image: linear-gradient( + to right, + transparent 0%, + black 2%, + black 90%, + transparent 100% + ); + + // Force grid to single row with fixed column sizes + .list.grid { + display: grid !important; + grid-template-columns: repeat(6, 320px) !important; + grid-template-rows: 1fr !important; + grid-auto-flow: column !important; + gap: 1.5rem !important; + width: max-content !important; + max-width: none !important; + } + + // Remove Bootstrap column classes + .list.grid > [class*="g-col"] { + grid-column: auto !important; + max-width: none !important; + width: auto !important; + } + + .quarto-grid-item { + background: rgba(255, 255, 255, 0.8); + border-radius: 12px; + overflow: hidden; + border: 1px solid rgba(148, 210, 189, 0.3); + transition: transform 0.2s ease, box-shadow 0.2s ease; + scroll-snap-align: start; + + &:hover { + transform: translateY(-4px); + box-shadow: 0 8px 24px rgba(0, 95, 115, 0.15); + } + + .card-img-top { + width: 100%; + height: auto; + } + + .card-title { + font-size: 0.95rem; + padding: 0.5rem 0.75rem; + margin: 0; + text-align: center; + + a { + text-decoration: none; + color: var(--brand-darkteal); + } + } + + .card-body { + padding: 0; + } + } + } + + .see-all-link { + display: inline-block; + margin-top: 1.5rem; + font-weight: 500; + color: var(--brand-darkteal); + } } +// Hide category badges on gallery example pages body:has(.quarto-title) .quarto-categories { display: none; } @@ -385,7 +568,7 @@ body:has(.quarto-title) .quarto-categories { .btn { display: inline-block; padding: 0.75rem 1.5rem; - border-radius: 6px; + border-radius: 50rem; font-weight: 500; text-decoration: none; transition: all 0.2s ease; @@ -405,6 +588,20 @@ body:has(.quarto-title) .quarto-categories { border-color: var(--brand-teal, #0A9396); color: white; text-decoration: none; + box-shadow: 0 0 20px rgba(0, 95, 115, 0.3); + transform: translateY(-2px); + } + } + + &.btn-outline-light { + background: transparent; + color: var(--brand-darkteal, #005F73); + border: 2px solid var(--brand-darkteal, #005F73); + + &:hover { + background: var(--brand-darkteal, #005F73); + color: white; + text-decoration: none; } } From be55dfdc094b05ef55e865889210d784c37c815a Mon Sep 17 00:00:00 2001 From: Thomas Lin Pedersen Date: Sun, 15 Mar 2026 16:58:33 +0100 Subject: [PATCH 2/2] better aria support in heading --- doc/index.qmd | 2 +- doc/styles.scss | 27 +++++++++++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/doc/index.qmd b/doc/index.qmd index a0b02066..ca61e912 100644 --- a/doc/index.qmd +++ b/doc/index.qmd @@ -21,7 +21,7 @@ include-in-header: ::: {.hero-banner} -# QUERY ![](assets/visualize.svg){.hviz} UNDERSTAND +# QUERY ![](assets/visualize.svg){.hviz aria-hidden="true"}VISUALIZE UNDERSTAND ::: {.hero-body} ggsql brings the elegance of the Grammar of Graphics to SQL. Write familiar queries, add visualization clauses, and see your data transform into beautiful, composable charts — no context switching, no separate tools, just SQL with superpowers. diff --git a/doc/styles.scss b/doc/styles.scss index 9534ffe0..9f98455c 100644 --- a/doc/styles.scss +++ b/doc/styles.scss @@ -56,14 +56,33 @@ body:has(.hero-banner) { font-weight: 100; } - .hviz { - display: inline; - height: 0.85em; - width: auto; + .hviz-wrapper { + position: relative; + display: inline-block; vertical-align: baseline; margin: 0 0.1em; + } + + .hviz-wrapper .hviz { + display: block; + height: 0.85em; + width: auto; + margin: 0; position: relative; top: 0.08em; + pointer-events: none; + user-select: none; + } + + // Accessible + selectable text overlay + .hviz-wrapper .sr-only { + position: absolute; + inset: 0; + color: transparent; + user-select: all; + display: flex; + align-items: center; + justify-content: center; } .hund {