From c93b97a8513572b1bdcc4ab4977b2494a6483238 Mon Sep 17 00:00:00 2001 From: O L Date: Mon, 26 Oct 2026 15:58:41 +0000 Subject: [PATCH 1/8] Refine official site with a restrained blue visual identity --- CHANGELOG.md | 2 + examples/official-site/assets/brand/brand.css | 5 +- .../assets/brand/logo-horizontal-light.svg | 4 +- .../assets/landing/styles/base.css | 4 +- .../assets/landing/styles/comparison.css | 8 +- .../assets/landing/styles/component-demos.css | 12 +- .../assets/landing/styles/demo.css | 22 ++-- .../assets/landing/styles/frontend.css | 10 +- .../assets/landing/styles/hero.css | 97 +++----------- .../assets/landing/styles/interaction.css | 6 +- .../assets/landing/styles/navigation.css | 122 ++---------------- .../assets/landing/styles/responsive.css | 56 ++------ .../assets/landing/styles/ribbon.css | 17 +-- .../assets/landing/styles/scene.css | 2 +- .../assets/landing/styles/sections.css | 40 +++--- .../assets/landing/styles/waves.css | 9 +- .../sqlpage/templates/landing-page.handlebars | 6 +- 17 files changed, 108 insertions(+), 314 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1c98381..ffa036be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## unreleased +- Refined the official website with muted blue accents, simpler navigation, a clearer primary action, and a level SQL ribbon while preserving the interactive sculpture and its physical reflections. + - Restored parameter reuse for repeated variables on SQL Server, PostgreSQL, and SQLite, fixing SQL Server and PostgreSQL queries that repeat a variable-dependent expression in `SELECT` and `GROUP BY`. ## v0.47.0 (2026-10-15) diff --git a/examples/official-site/assets/brand/brand.css b/examples/official-site/assets/brand/brand.css index 9425a715..5289e298 100644 --- a/examples/official-site/assets/brand/brand.css +++ b/examples/official-site/assets/brand/brand.css @@ -9,8 +9,9 @@ --brand-ink: #080e11; --brand-graphite: #18262d; --brand-paper: #f4f7f8; - --brand-cyan: #58cce0; - --brand-teal: #146575; + --brand-accent: #8bb8e8; + --brand-cyan: var(--brand-accent); + --brand-teal: #365b82; --brand-steel: #9caeb5; --brand-font: Outfit, system-ui, sans-serif; --brand-mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace; diff --git a/examples/official-site/assets/brand/logo-horizontal-light.svg b/examples/official-site/assets/brand/logo-horizontal-light.svg index 45132597..8923e69c 100644 --- a/examples/official-site/assets/brand/logo-horizontal-light.svg +++ b/examples/official-site/assets/brand/logo-horizontal-light.svg @@ -1,11 +1,11 @@ - + - + diff --git a/examples/official-site/assets/landing/styles/base.css b/examples/official-site/assets/landing/styles/base.css index 86e0706b..50d767c6 100644 --- a/examples/official-site/assets/landing/styles/base.css +++ b/examples/official-site/assets/landing/styles/base.css @@ -1,7 +1,7 @@ /* Shared tokens and native element defaults. */ :root { --foreground: #f4f7f8; - --cyan: #58cce0; + --cyan: var(--brand-accent); --ink: #080e11; --mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace; } @@ -58,7 +58,7 @@ button:focus-visible, outline-offset: 6px; } ::selection { - background: #5bcce1; + background: var(--brand-accent); color: #081015; } .skip-link { diff --git a/examples/official-site/assets/landing/styles/comparison.css b/examples/official-site/assets/landing/styles/comparison.css index 8853a8ec..3369ea95 100644 --- a/examples/official-site/assets/landing/styles/comparison.css +++ b/examples/official-site/assets/landing/styles/comparison.css @@ -16,10 +16,10 @@ padding-bottom: calc(var(--wave-height) + 64px); } .comparison-section .eyebrow { - color: #146575; + color: #315375; } .comparison-section h2 em { - color: #0e8292; + color: #3d6892; } .comparison-section .section-heading { grid-template-columns: minmax(0, 1.35fr) minmax(0, 0.95fr) minmax(0, 0.27fr); @@ -208,7 +208,7 @@ font: 12px / 1.4 var(--mono); } .comparison-deploy svg { - color: #29d6df; + color: #5e9edf; width: 18px; height: 18px; } @@ -508,7 +508,7 @@ } .comparison-controls button:not(:disabled):hover { background: #e1ecef; - border-color: #146575; + border-color: #315375; } .comparison-controls svg { width: 20px; diff --git a/examples/official-site/assets/landing/styles/component-demos.css b/examples/official-site/assets/landing/styles/component-demos.css index e29f6e9c..8bae9ba7 100644 --- a/examples/official-site/assets/landing/styles/component-demos.css +++ b/examples/official-site/assets/landing/styles/component-demos.css @@ -30,7 +30,7 @@ font: inherit; } .sql-editor-header button:hover { - color: #64d5df; + color: #64a2df; } .source-tabs { display: flex; @@ -130,7 +130,7 @@ } .component-picker button:hover { transform: translateX(3px); - background: #29c8d710; + background: #5a99d710; } .component-picker button[aria-selected="true"] { background: #07111d; @@ -140,7 +140,7 @@ box-shadow: 0 12px 28px #07111d30; } .component-picker button[aria-selected="true"] svg { - color: #29c8d7; + color: #5a99d7; } #demo-panel { position: relative; @@ -185,7 +185,7 @@ background: #080e1b; color: #b3c5d4; font: 13px / 1.8 var(--mono); - border: 1px solid #28c8d6; + border: 1px solid #5a98d6; border-radius: 12px; padding: 22px; white-space: pre-wrap; @@ -200,7 +200,7 @@ align-items: center; gap: 6px; font: 12px var(--mono); - color: #217c8a; + color: #3a628a; } .demo-docs:hover { text-decoration: underline; @@ -287,7 +287,7 @@ } #demo-source .hljs-built_in, #demo-source .hljs-type { - color: #6cd6e8; + color: #6caae8; } #demo-panel[data-demo="catalog"] .sql-editor, #demo-panel[data-demo="catalog"] .demo-annotation, diff --git a/examples/official-site/assets/landing/styles/demo.css b/examples/official-site/assets/landing/styles/demo.css index 82523924..1e37e867 100644 --- a/examples/official-site/assets/landing/styles/demo.css +++ b/examples/official-site/assets/landing/styles/demo.css @@ -5,7 +5,7 @@ --tblr-body-bg: #0d1224; --tblr-bg-surface: #0d1224; --tblr-border-color: #273149; - --tblr-primary: #28c8d6; + --tblr-primary: #5a98d6; --tblr-body-color: #dbe0ec; } html, @@ -155,9 +155,9 @@ form.mt-3 { font-size: 13px; padding: 9px 18px; border-radius: 8px; - box-shadow: 0 3px 14px #20c8d518; - background: #27c4d2; - border-color: #27c4d2; + box-shadow: 0 3px 14px #5997d518; + background: #5895d2; + border-color: #5895d2; color: #06121b; } .apexcharts-tooltip { @@ -211,7 +211,7 @@ form.mt-3 { white-space: normal; } #landing-kpis .text-cyan { - color: #43ccd7 !important; + color: #5a99d7 !important; } @media (max-width: 480px) { #sqlpage_main_wrapper { @@ -258,7 +258,7 @@ form.mt-3 { #landing-profile .input-group-text { background: #111c2e; border-color: #273149; - color: #73a4b7; + color: #7395b7; } #landing-profile .input-group-text svg { width: 17px; @@ -272,8 +272,8 @@ form.mt-3 { box-shadow 180ms; } #landing-profile .form-control:focus { - border-color: #28c8d6; - box-shadow: 0 0 0 3px #28c8d61a; + border-color: #5a98d6; + box-shadow: 0 0 0 3px #5a98d61a; } #landing-profile .form-hint { font-size: 11px; @@ -319,15 +319,15 @@ form.mt-3 { } .component-catalog a:hover, .component-catalog a:focus-visible { - color: #5ddddf; + color: #5e9edf; background: #122636; - border-color: #28c8d6; + border-color: #5a98d6; } .component-catalog svg { width: 19px; height: 19px; flex: none; - color: #67b9c7; + color: #6797c7; } .component-catalog svg:last-child { margin-left: auto; diff --git a/examples/official-site/assets/landing/styles/frontend.css b/examples/official-site/assets/landing/styles/frontend.css index 4998ee65..9a89a5b4 100644 --- a/examples/official-site/assets/landing/styles/frontend.css +++ b/examples/official-site/assets/landing/styles/frontend.css @@ -1,10 +1,10 @@ /* Four frontend views pair concise copy with diagrams and deployment links. */ .deployment-section { - --section-color: #2bc5cf; + --section-color: #5793cf; /* Match the solid wave at the edge, then retain the section's diagonal gradient. */ background: linear-gradient(180deg, var(--section-color), transparent 180px), - linear-gradient(125deg, #29d6df, #2ba4b4); + linear-gradient(125deg, #5e9edf, #4c80b4); color: #071322; padding-top: 65px; padding-bottom: 150px; @@ -136,7 +136,7 @@ margin-top: 7px; } .feature-result { - background: linear-gradient(120deg, #29d6df, #2ba4b4); + background: linear-gradient(120deg, #5e9edf, #4c80b4); color: #071322; font: 700 20px / 1.2 var(--brand-font); } @@ -239,10 +239,10 @@ color: #081b2b; font-size: 13px; white-space: nowrap; - background: linear-gradient(90deg, #408fa2, #6c9ec3); + background: linear-gradient(90deg, #4473a2, #6c9ec3); } .stream-fill { - background: linear-gradient(90deg, #39abc0, #79b6e8); + background: linear-gradient(90deg, #5188c0, #79b6e8); } .stream-sheen { width: 35%; diff --git a/examples/official-site/assets/landing/styles/hero.css b/examples/official-site/assets/landing/styles/hero.css index f10dc8e5..6b4e9388 100644 --- a/examples/official-site/assets/landing/styles/hero.css +++ b/examples/official-site/assets/landing/styles/hero.css @@ -13,8 +13,8 @@ position: relative; overflow: hidden; padding: 19px 24px 20px; - border: 0; - border-radius: 3px; + border: 1px solid #8bb8e833; + border-radius: 6px; background: repeating-linear-gradient( 0deg, @@ -118,7 +118,7 @@ font-size: 0.985em; letter-spacing: 0; padding: 4px 0 9px; - background: linear-gradient(180deg, #83deea 0%, #58c8da 48%, #39adc4 100%); + background: linear-gradient(180deg, #a3c7ed 0%, #8bb8e8 48%, #78a4d3 100%); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; @@ -145,91 +145,32 @@ .intro-row p { font-size: clamp(17px, 1.5vw, 27px); line-height: 1.35; - font-weight: 550; + font-weight: 450; letter-spacing: -0.01em; margin: 0; flex: 1; - color: #d5d7d9; + color: #a9b9c9; white-space: nowrap; text-shadow: 0 2px 4px #0009; } .run-link { - display: flex; + display: inline-flex; align-items: center; - gap: 17px; - color: #5fcfe2; - font-size: clamp(20px, 1.8vw, 29px); + justify-content: center; + gap: 16px; + padding: 16px 24px; + border: 1px solid #a3c7ed55; + border-radius: 6px; + background: var(--brand-accent); + color: #0b1724; + font-size: clamp(18px, 1.4vw, 23px); + font-weight: 600; white-space: nowrap; - transition: color 0.25s; - letter-spacing: -0.01em; -} -.run-orbit { - width: 70px; - height: 70px; - position: relative; - display: grid; - place-items: center; - border: 1px solid #4fb6ca70; - border-radius: 50%; - transition: box-shadow 0.3s; -} -.run-orbit > span { - position: absolute; - inset: 6px; - border: 5px solid #56bfd0; - border-radius: 50%; - display: grid; - place-items: center; - box-shadow: - inset 0 0 12px #42daf31a, - 0 0 13px #42daf31a; - transition: - background 0.3s, - color 0.3s; -} -.run-orbit > span .icon { - margin-left: 3px; - --icon-size: 21px; -} -.run-orbit > i { - position: absolute; - inset: -6px; - border-top: 2px solid #57c5db; - border-right: 2px solid transparent; - border-bottom: 2px solid #57c5db; - border-left: 2px solid transparent; - border-radius: 50%; - transform: rotate(25deg); - transition: transform 0.55s; -} -.run-orbit > i:before, -.run-orbit > i:after { - content: ""; - position: absolute; - width: 4px; - height: 4px; - top: 10px; - right: 9px; - background: #6bd7e9; - box-shadow: 0 0 8px #43c9ed; - border-radius: 50%; -} -.run-orbit > i:after { - top: auto; - right: auto; - bottom: 10px; - left: 9px; + transition: background-color 0.2s; } .run-link:hover { - color: #b7f2ff; -} -.run-link:hover .run-orbit { - box-shadow: 0 0 32px #59d7ed22; -} -.run-link:hover .run-orbit > i { - transform: rotate(100deg); + background: #a3c7ed; } -.run-link:hover .run-orbit > span { - background: #51c1d0; - color: #07171c; +.run-link .icon { + --icon-size: 22px; } diff --git a/examples/official-site/assets/landing/styles/interaction.css b/examples/official-site/assets/landing/styles/interaction.css index 6c269858..9dde5507 100644 --- a/examples/official-site/assets/landing/styles/interaction.css +++ b/examples/official-site/assets/landing/styles/interaction.css @@ -24,7 +24,7 @@ cursor: grabbing; } .model-interaction:focus-visible { - outline: 1px dashed #5ed7df77; + outline: 1px dashed #5e9edf77; } .scene-error { position: absolute; @@ -49,8 +49,8 @@ color: #deecee; } .scene-error button { - border: 1px solid #458897; - color: #70c8da; + border: 1px solid #456e97; + color: #70a5da; background: #15313c; padding: 8px 17px; border-radius: 50px; diff --git a/examples/official-site/assets/landing/styles/navigation.css b/examples/official-site/assets/landing/styles/navigation.css index 3392e5b6..46465fcc 100644 --- a/examples/official-site/assets/landing/styles/navigation.css +++ b/examples/official-site/assets/landing/styles/navigation.css @@ -25,137 +25,31 @@ width: 72px; height: auto; flex-shrink: 0; - filter: drop-shadow(0 0 10px #37e5ef1a); + filter: drop-shadow(0 0 10px #64aaef1a); } -/* Thin, smoked glass with a polished edge. */ +/* Quiet navigation leaves the sculpture as the page's reflective surface. */ .desktop-nav { display: flex; align-items: center; gap: 18px; } .desktop-nav .glass-button { - --glass-x: 50%; - --glass-y: 20%; - position: relative; - isolation: isolate; display: inline-flex; align-items: center; justify-content: center; - overflow: hidden; min-height: 46px; padding: 12px 22px; - border: 1px solid transparent; - border-radius: 999px; + border-radius: 6px; font-size: clamp(14px, 1.32vw, 21px); - color: #d7e0e4; - background: - radial-gradient( - ellipse at var(--glass-x) var(--glass-y), - #9cdef715, - transparent 68% - ), - linear-gradient(118deg, #b4e9fa0b, transparent 38%, #74c4de08), - linear-gradient(180deg, #263c482b 0%, #0711178c 54%, #18333d55 100%); - backdrop-filter: blur(17px) saturate(1.22) brightness(1.08); - -webkit-backdrop-filter: blur(17px) saturate(1.22) brightness(1.08); - box-shadow: - inset 0 1px 1px #e3f6ff19, - inset 1px 0 1px #a7eaff0c, - inset 0 -1.5px 1px #62b8cc30, - inset 0 -5px 9px #56cae508, - 0 5px 16px #0005, - 0 1px 2px #0008; + color: #c3cfdb; transition: - box-shadow 0.32s, - color 0.25s, - transform 0.25s; -} -.desktop-nav .glass-button:before { - content: ""; - position: absolute; - inset: 0; - border-radius: inherit; - padding: 1px; - background: linear-gradient( - 135deg, - #a5d9e638 0%, - #5c9daf38 22%, - #142f3f14 46%, - #46a0b16b 76%, - #b2edf24d 100% - ); - -webkit-mask: - linear-gradient(#000 0 0) content-box, - linear-gradient(#000 0 0); - -webkit-mask-composite: xor; - mask: - linear-gradient(#000 0 0) content-box, - linear-gradient(#000 0 0); - mask-composite: exclude; - pointer-events: none; -} -.desktop-nav .glass-button:after { - content: ""; - position: absolute; - top: 1px; - left: 12%; - right: 12%; - height: 42%; - border-radius: 50% 50% 45% 45% / 15% 15% 75% 75%; - background: linear-gradient(165deg, #dcf4ff0f, transparent 62%); - border-top: 1px solid #e9fbff19; - pointer-events: none; - opacity: 0.65; - transition: opacity 0.3s; -} -.desktop-nav .glass-button > span { - position: relative; - z-index: 1; - letter-spacing: -0.015em; - text-shadow: - 0 1px 2px #000b, - 0 -1px 0 #ffffff08; -} -.desktop-nav .glass-button:hover:after, -.desktop-nav .glass-button:focus-visible:after { - opacity: 1; -} -.desktop-nav .glass-button:active { - transform: translateY(0.5px); - box-shadow: - inset 0 2px 7px #0005, - inset 0 -1px 1px #8cdded50, - 0 1px 7px #0005; + color 0.2s, + background-color 0.2s; } .desktop-nav .glass-button:hover, .desktop-nav .glass-button:focus-visible { - color: #f0f9fc; - transform: translateY(-1px); - border-color: #67d9ea99; - background: - radial-gradient( - ellipse at var(--glass-x) var(--glass-y), - #a6e7fa24, - transparent 72% - ), - linear-gradient(180deg, #47718345, #15334163 52%, #21566950); - box-shadow: - inset 0 1px 1px #e5f8ff29, - inset 0 -1.5px 2px #90e8f378, - inset 0 -6px 12px #75d7ee18, - 0 5px 17px #0004, - 0 2px 16px #52d7f12b; -} -.desktop-nav .glass-button:hover:before, -.desktop-nav .glass-button:focus-visible:before { - background: linear-gradient( - 137deg, - #c7f4ff5c, - #4591a464 25%, - #2350673b 45%, - #74e0f6b3 76%, - #caf8ff8f - ); + color: var(--foreground); + background: #8bb8e810; } .mobile-menu { display: none; diff --git a/examples/official-site/assets/landing/styles/responsive.css b/examples/official-site/assets/landing/styles/responsive.css index 36c7573d..c5ac230e 100644 --- a/examples/official-site/assets/landing/styles/responsive.css +++ b/examples/official-site/assets/landing/styles/responsive.css @@ -8,10 +8,6 @@ .viewport-footer { bottom: 30px; } - .run-orbit { - height: 82px; - width: 82px; - } } @media (max-width: 1150px) { .site-header { @@ -50,16 +46,6 @@ font-size: 22px; gap: 12px; } - .run-orbit { - width: 57px; - height: 57px; - } - .run-orbit > span { - border-width: 4px; - } - .run-orbit > span .icon { - --icon-size: 18px; - } .ribbon-sequence code { font-size: 17px; } @@ -106,14 +92,6 @@ font-size: 20px; gap: 11px; } - .run-orbit { - width: 50px; - height: 50px; - } - .run-orbit > span { - inset: 5px; - border-width: 3px; - } .scene-error { right: 6%; font-size: 12px; @@ -124,10 +102,7 @@ animation: none; } a, - button, - .run-orbit, - .run-orbit > span, - .run-orbit > i { + button { transition: none; } } @@ -176,8 +151,8 @@ border-radius: 5px; box-shadow: inset 0 2px 8px #000b, - 0 0 0 1px #38c9e52b, - 0 0 10px #38c9e511; + 0 0 0 1px #60a3e52b, + 0 0 10px #60a3e511; } .metal-recess code { grid-template-columns: 1fr; @@ -234,24 +209,13 @@ font-size: 20px; gap: 13px; } - .run-orbit { - width: clamp(46px, 13vw, 70px); - height: clamp(46px, 13vw, 70px); - } - .run-orbit > span { - inset: 5px; - border-width: 3px; - } - .run-orbit > span .icon { - --icon-size: 18px; - } .viewport .sql-ribbon { top: var(--portrait-ribbon-top, 78%); bottom: auto; height: 49px; - transform: rotate(-4deg); - width: 112%; - left: -6%; + transform: none; + width: 92%; + left: 4%; } .ribbon-sequence a { gap: 11px; @@ -310,12 +274,12 @@ height: 44px; padding: 0; flex-shrink: 0; - border: 1px solid #8acbdd35; + border: 1px solid #8ab4dd35; border-radius: 50%; background: linear-gradient(150deg, #b2e6fa14, #06121d55); box-shadow: inset 0 1px 1px #d9f5ff21, - inset 0 -1px 1px #57bdcc4d, + inset 0 -1px 1px #5792cc4d, 0 4px 12px #0005; backdrop-filter: blur(15px); } @@ -356,10 +320,6 @@ .intro-row p { font-size: 17px; } - .run-orbit { - width: 44px; - height: 44px; - } .viewport .sql-ribbon { height: 48px; bottom: 7%; diff --git a/examples/official-site/assets/landing/styles/ribbon.css b/examples/official-site/assets/landing/styles/ribbon.css index ddb8f5d3..a6b0d6af 100644 --- a/examples/official-site/assets/landing/styles/ribbon.css +++ b/examples/official-site/assets/landing/styles/ribbon.css @@ -2,22 +2,17 @@ .sql-ribbon { position: absolute; bottom: 4.5%; - left: -4%; - width: 110%; + left: 4%; + width: 92%; z-index: 12; height: clamp(54px, 5.6svh, 76px); display: flex; align-items: center; - transform: rotate(-4deg); + transform: none; transform-origin: center; - border-top: 1px solid #4195a5; - border-bottom: 1px solid #23515e; + border: 1px solid #8bb8e830; + border-radius: 8px; background: linear-gradient(180deg, #15252cc7, #091118ed 45%, #0a1117f5); - box-shadow: - 0 -6px 23px #41bbd513, - inset 0 1px 0 #66cfe12b, - inset 0 7px 13px #0006, - 0 8px 24px #0005; opacity: var(--ribbon-opacity); overflow: hidden; } @@ -53,7 +48,7 @@ color: #89bac7; } .ribbon-sequence a:hover { - color: #87d6e8; + color: #87b8e8; } .viewport-footer { position: absolute; diff --git a/examples/official-site/assets/landing/styles/scene.css b/examples/official-site/assets/landing/styles/scene.css index 917173a2..5873f96b 100644 --- a/examples/official-site/assets/landing/styles/scene.css +++ b/examples/official-site/assets/landing/styles/scene.css @@ -26,7 +26,7 @@ overflow: hidden; isolation: isolate; background: - radial-gradient(ellipse at 79% 26%, #183d4755, transparent 57%), #080e11; + radial-gradient(ellipse at 79% 26%, #233b5555, transparent 57%), #080e11; } /* One sculpture follows the authored section anchors. */ .scene-layer { diff --git a/examples/official-site/assets/landing/styles/sections.css b/examples/official-site/assets/landing/styles/sections.css index 6710501a..392038c9 100644 --- a/examples/official-site/assets/landing/styles/sections.css +++ b/examples/official-site/assets/landing/styles/sections.css @@ -19,11 +19,11 @@ } .landing-section h2 em { font-style: normal; - color: #29c8d7; + color: #5a99d7; } .eyebrow { font: 13px / 1.65 var(--mono); - color: #5b9ba9; + color: #5b82a9; } .section-heading { display: grid; @@ -66,7 +66,7 @@ overflow-anchor: none; } .demo-section .eyebrow { - color: #3f7480; + color: #3f6080; } .demo-section h2 { font-size: clamp(60px, 7.7vw, 106px); @@ -125,7 +125,7 @@ } .segmented button[aria-pressed="true"], .segmented button[aria-selected="true"] { - background: #29c8d7; + background: #5a99d7; color: #07121d; } .stack-list { @@ -153,7 +153,7 @@ color: #a9b5c7; } .stack-list .database-layer { - border-color: #29c8d7; + border-color: #5a99d7; background: #0b1121; } .stack-list .stack-detail { @@ -173,7 +173,7 @@ .stack-detail [data-stack-copy="sqlpage"] { opacity: 0; transform: translateY(8px); - color: #63d6df; + color: #63a1df; } .stack-list li:nth-child(2) { --stack-delay: 45ms; @@ -185,7 +185,7 @@ --stack-delay: 135ms; } .stack-comparison[data-stack="sqlpage"] [data-stack-layer] { - border-color: #29c8d750; + border-color: #5a99d750; background: #102333; } .stack-comparison[data-stack="sqlpage"] [data-stack-copy="typical"] { @@ -211,7 +211,7 @@ transform 0.2s; } .benefit:hover { - border-color: #29c8d7; + border-color: #5a99d7; transform: translateY(-3px); } .benefit h3 { @@ -229,7 +229,7 @@ font: bold 12px var(--mono); } .benefit.featured { - background: linear-gradient(120deg, #28d4dd, #2ba6b6); + background: linear-gradient(120deg, #5d9ddd, #4c81b6); color: #071120; border-color: transparent; } @@ -240,15 +240,15 @@ display: inline-flex; align-items: center; gap: 5px; - border: 1px solid #2c8193; + border: 1px solid #3e6893; padding: 5px 9px; margin: 3px 0; border-radius: 999px; font: 11px / 1.3 var(--mono); - color: #41c6d6; + color: #5a98d6; } a.chip:hover { - background: #29c8d7; + background: #5a99d7; color: #07121d; } .chip svg { @@ -266,10 +266,10 @@ a.chip:hover { margin-top: 120px; } .database-grid a { - border: 1px solid #2acbda; + border: 1px solid #5c9bda; border-radius: 12px; background: #08111e; - color: #32c9d9; + color: #5b9ad9; padding: 28px 22px; font: 24px var(--mono); transform: rotate(-2deg); @@ -285,13 +285,13 @@ a.chip:hover { .database-grid a:focus-visible { transform: rotate(0) translateY(-5px); background: #102337; - box-shadow: 0 5px 25px #23c8d522; + box-shadow: 0 5px 25px #5997d522; } .database-grid small { display: block; margin-top: 9px; font-size: 11px; - color: #588a9c; + color: #587a9c; } .emoji { font-family: @@ -314,7 +314,7 @@ a.chip:hover { flex-direction: column; } .commit-section h2 { - color: #2fc7d4; + color: #5997d4; } .commit-action { display: flex; @@ -324,7 +324,7 @@ a.chip:hover { } .primary-button { display: inline-block; - background: linear-gradient(110deg, #2bd5df, #289cac); + background: linear-gradient(110deg, #5e9edf, #487aac); color: #071221; border-radius: 8px; padding: 22px 36px; @@ -339,7 +339,7 @@ a.chip:hover { } .primary-button:hover { transform: translateY(-3px); - box-shadow: 0 6px 28px #2acddb44; + box-shadow: 0 6px 28px #5c9bdb44; } .landing-footer { margin-top: auto; @@ -362,7 +362,7 @@ a.chip:hover { color: #90a8bd; } .landing-footer nav a:hover { - color: #32c9d9; + color: #5b9ad9; text-decoration: underline; } @media (max-width: 900px) { diff --git a/examples/official-site/assets/landing/styles/waves.css b/examples/official-site/assets/landing/styles/waves.css index 5d487f3b..bcac4c66 100644 --- a/examples/official-site/assets/landing/styles/waves.css +++ b/examples/official-site/assets/landing/styles/waves.css @@ -13,12 +13,12 @@ fill: var(--section-color); } .wave-front { - stroke: #39d3e5; + stroke: #60a3e5; stroke-width: 0.7; } .wave-back { opacity: 0.24; - fill: #39d3e5; + fill: #60a3e5; } @media (max-width: 900px) { .landing-section { @@ -30,3 +30,8 @@ --wave-height: 96px; } } + +/* Keep the first crest below the opening ribbon and footer. */ +.demo-section { + --wave-height: 24px; +} diff --git a/examples/official-site/sqlpage/templates/landing-page.handlebars b/examples/official-site/sqlpage/templates/landing-page.handlebars index c0bb37fc..c5c085e1 100644 --- a/examples/official-site/sqlpage/templates/landing-page.handlebars +++ b/examples/official-site/sqlpage/templates/landing-page.handlebars @@ -147,11 +147,7 @@ Run itRun it diff --git a/tests/end-to-end/landing-streaming.spec.ts b/tests/end-to-end/landing-streaming.spec.ts index 71e6f4c3..0282893a 100644 --- a/tests/end-to-end/landing-streaming.spec.ts +++ b/tests/end-to-end/landing-streaming.spec.ts @@ -11,6 +11,23 @@ test("streaming comparison starts early, pauses and restarts with its tab", asyn diagram.getByText("streaming HTML", { exact: true }), ).toBeVisible(); const bounds = await diagram.boundingBox(); + // Filling a bar must visibly differ from both its rail and the panel surface. + expect( + await diagram.evaluate((element) => { + const panel = getComputedStyle( + element.closest(".frontend-panels")!, + ).backgroundColor; + const fills = [...element.querySelectorAll(".timeline-fill")]; + const stream = element.querySelector(".stream-timeline")!; + return ( + fills.every( + (fill) => getComputedStyle(fill).backgroundColor !== panel, + ) && + getComputedStyle(stream.querySelector(".stream-fill")!) + .backgroundColor !== getComputedStyle(stream).backgroundColor + ); + }), + ).toBe(true); // Seek the real animations to the moment SQLPage has streamed HTML while // the illustrative app is still loading JavaScript, before its API/render. diff --git a/tests/end-to-end/landing.spec.ts b/tests/end-to-end/landing.spec.ts index a1f828ac..b46049e8 100644 --- a/tests/end-to-end/landing.spec.ts +++ b/tests/end-to-end/landing.spec.ts @@ -89,7 +89,7 @@ for (const viewport of [ const canvas = page.locator("canvas"); await expect(canvas).toHaveJSProperty( "width", - viewport.width === 390 ? 320 : 1000, + viewport.width === 390 ? 400 : 1000, ); if (viewport.width === 390) { await expect(page.locator(".sqlpage-world")).toHaveAttribute( @@ -100,10 +100,10 @@ for (const viewport of [ expect.stringMatching(/\/sculpture-turns\.avif$/), ]); } else { - // Keep the full raster while batching nodes and cropping only empty glow. - await expect(page.locator("html")).toHaveAttribute( + // The grounded studio has no instanced orbital dots. + await expect(page.locator("html")).not.toHaveAttribute( "data-test-orb-instances", - "8", + /.+/, ); await expect(page.locator("html")).toHaveAttribute( "data-test-glow-pixels", @@ -161,7 +161,7 @@ for (const viewport of [ y: rect.y + scrollY, size: rect.width, turn: Number(canvas.dataset.scrollTurn), - pixels: canvas.width === 320 ? canvas.toDataURL() : null, + pixels: canvas.width === 400 ? canvas.toDataURL() : null, opacity: Number( getComputedStyle(canvas.closest(".scene-layer")!).opacity, ), From e482d74a8a1428f5b998810c18807d32c88694df Mon Sep 17 00:00:00 2001 From: O L Date: Mon, 26 Oct 2026 20:51:45 +0000 Subject: [PATCH 7/8] Preserve Tabler utility colors and use surface tokens for landing backgrounds --- examples/official-site/assets/brand/brand.css | 8 +++++--- .../assets/landing/styles/comparison.css | 4 ++-- .../assets/landing/styles/component-demos.css | 12 ++++++------ .../official-site/assets/landing/styles/demo.css | 10 +++++----- .../official-site/assets/landing/styles/dialog.css | 7 ++++--- .../official-site/assets/landing/styles/frontend.css | 10 +++++----- .../official-site/assets/landing/styles/hero.css | 4 ++-- .../assets/landing/styles/interaction.css | 2 +- .../assets/landing/styles/responsive.css | 2 +- .../official-site/assets/landing/styles/ribbon.css | 4 ++-- .../assets/landing/styles/section-motion.css | 2 +- .../official-site/assets/landing/styles/sections.css | 10 +++++----- 12 files changed, 39 insertions(+), 36 deletions(-) diff --git a/examples/official-site/assets/brand/brand.css b/examples/official-site/assets/brand/brand.css index 5a82e159..a3309cbe 100644 --- a/examples/official-site/assets/brand/brand.css +++ b/examples/official-site/assets/brand/brand.css @@ -13,11 +13,13 @@ --tblr-font-sans-serif: Outfit, system-ui, sans-serif; --tblr-font-monospace: "SFMono-Regular", Consolas, "Liberation Mono", monospace; + /* Native Tabler utility colors also serve component foregrounds. Keep their + defaults; use the surface properties below for dark page backgrounds. */ --tblr-black: #000; --tblr-white: #fff; - --tblr-light: #f4f7f8; - --tblr-dark: #080d19; - --tblr-gray-300: #dadfe5; + --tblr-light: #f9fafb; + --tblr-dark: #1f2937; + --tblr-gray-300: #d1d5db; --tblr-primary-fg: var(--tblr-white); /* Core colors refined for better contrast */ diff --git a/examples/official-site/assets/landing/styles/comparison.css b/examples/official-site/assets/landing/styles/comparison.css index bf4fec74..870cbb0d 100644 --- a/examples/official-site/assets/landing/styles/comparison.css +++ b/examples/official-site/assets/landing/styles/comparison.css @@ -129,7 +129,7 @@ } .comparison-code { border-color: var(--tblr-bg-surface-tertiary); - background: var(--tblr-dark); + background: var(--tblr-body-bg); color: var(--tblr-light); } .comparison-window-bar { @@ -179,7 +179,7 @@ repeating-linear-gradient( 90deg, transparent 0 7px, - var(--tblr-dark) 7px 9px + var(--tblr-body-bg) 7px 9px ), linear-gradient( 90deg, diff --git a/examples/official-site/assets/landing/styles/component-demos.css b/examples/official-site/assets/landing/styles/component-demos.css index 12da0487..3c7ab349 100644 --- a/examples/official-site/assets/landing/styles/component-demos.css +++ b/examples/official-site/assets/landing/styles/component-demos.css @@ -7,9 +7,9 @@ margin-top: 18px; border: 1px solid var(--tblr-bg-surface-tertiary); border-radius: 12px; - background: var(--tblr-dark); + background: var(--tblr-body-bg); box-shadow: 0 14px 28px - color-mix(in srgb, var(--tblr-dark) 12.55%, transparent); + color-mix(in srgb, var(--tblr-body-bg) 12.55%, transparent); overflow: hidden; } .sql-editor-header { @@ -134,12 +134,12 @@ background: color-mix(in srgb, var(--tblr-blue) 6.27%, transparent); } .component-picker button[aria-selected="true"] { - background: var(--tblr-dark); + background: var(--tblr-body-bg); color: white; border-color: var(--tblr-bg-surface-tertiary); border-left: 3px solid var(--tblr-primary); box-shadow: 0 12px 28px - color-mix(in srgb, var(--tblr-dark) 18.82%, transparent); + color-mix(in srgb, var(--tblr-body-bg) 18.82%, transparent); } .component-picker button[aria-selected="true"] svg { color: var(--tblr-blue); @@ -178,14 +178,14 @@ border-radius: 18px; background: var(--tblr-bg-surface); box-shadow: 0 18px 30px - color-mix(in srgb, var(--tblr-dark) 9.41%, transparent); + color-mix(in srgb, var(--tblr-body-bg) 9.41%, transparent); } #demo-panel iframe[aria-hidden="true"] { visibility: hidden; pointer-events: none; } .code-panel { - background: var(--tblr-dark); + background: var(--tblr-body-bg); color: var(--tblr-secondary-color); font: 13px / 1.8 var(--tblr-font-monospace); border: 1px solid var(--tblr-blue); diff --git a/examples/official-site/assets/landing/styles/demo.css b/examples/official-site/assets/landing/styles/demo.css index 1aa1ab90..a080b386 100644 --- a/examples/official-site/assets/landing/styles/demo.css +++ b/examples/official-site/assets/landing/styles/demo.css @@ -99,7 +99,7 @@ body { border-radius: 5px; background: var( --tblr-success-lt, - color-mix(in srgb, var(--tblr-success) 20%, var(--tblr-dark)) + color-mix(in srgb, var(--tblr-success) 20%, var(--tblr-body-bg)) ); color: var(--hljs-cosmic-string); font-size: 11px; @@ -112,13 +112,13 @@ body { .table tr.late td._col_status p { background: var( --tblr-warning-lt, - color-mix(in srgb, var(--tblr-warning) 20%, var(--tblr-dark)) + color-mix(in srgb, var(--tblr-warning) 20%, var(--tblr-body-bg)) ); color: color-mix(in srgb, var(--tblr-warning), var(--tblr-white) 30%); } input.form-control { min-height: 38px; - background: var(--tblr-dark); + background: var(--tblr-body-bg); border-color: var(--tblr-bg-surface-tertiary); font-size: 12px; border-radius: 8px; @@ -192,7 +192,7 @@ form.mt-3 { 120deg, var( --tblr-success-lt, - color-mix(in srgb, var(--tblr-success) 20%, var(--tblr-dark)) + color-mix(in srgb, var(--tblr-success) 20%, var(--tblr-body-bg)) ), var(--tblr-bg-surface-secondary) ) !important; @@ -309,7 +309,7 @@ form.mt-3 { 145deg, var( --tblr-success-lt, - color-mix(in srgb, var(--tblr-success) 20%, var(--tblr-dark)) + color-mix(in srgb, var(--tblr-success) 20%, var(--tblr-body-bg)) ), var(--tblr-bg-surface-secondary) ) !important; diff --git a/examples/official-site/assets/landing/styles/dialog.css b/examples/official-site/assets/landing/styles/dialog.css index edf24ad2..3468f2f9 100644 --- a/examples/official-site/assets/landing/styles/dialog.css +++ b/examples/official-site/assets/landing/styles/dialog.css @@ -5,8 +5,8 @@ max-height: 85svh; overflow: auto; color: var(--tblr-body-color); - background: color-mix(in srgb, var(--tblr-dark) 92.55%, transparent); - border: 1px solid var(--tblr-dark); + background: color-mix(in srgb, var(--tblr-body-bg) 92.55%, transparent); + border: 1px solid var(--tblr-body-bg); border-radius: 12px; padding: 30px; backdrop-filter: blur(24px); @@ -35,7 +35,8 @@ .navigation-dialog nav a { font-size: 20px; padding: 17px 0; - border-top: 1px solid color-mix(in srgb, var(--tblr-dark) 26.67%, transparent); + border-top: 1px solid + color-mix(in srgb, var(--tblr-body-bg) 26.67%, transparent); } .navigation-dialog nav a:hover { color: var(--tblr-primary); diff --git a/examples/official-site/assets/landing/styles/frontend.css b/examples/official-site/assets/landing/styles/frontend.css index 69347299..5153dc7c 100644 --- a/examples/official-site/assets/landing/styles/frontend.css +++ b/examples/official-site/assets/landing/styles/frontend.css @@ -60,9 +60,9 @@ background: color-mix(in srgb, var(--tblr-bg-surface) 8.63%, transparent); } .frontend-tabs button[aria-selected="true"] { - background: var(--tblr-dark); + background: var(--tblr-body-bg); color: var(--tblr-light); - border-color: var(--tblr-dark); + border-color: var(--tblr-body-bg); box-shadow: 0 12px 25px color-mix(in srgb, var(--tblr-bg-surface) 14.51%, transparent); } @@ -88,7 +88,7 @@ .frontend-panels { border-radius: 20px; padding: 55px 45px; - background: var(--tblr-dark); + background: var(--tblr-body-bg); color: var(--tblr-light); box-shadow: 0 25px 45px color-mix(in srgb, var(--tblr-bg-surface) 12.55%, transparent); @@ -201,7 +201,7 @@ .render-timeline > span { --stage-color: var(--tblr-primary); position: relative; - background: color-mix(in srgb, var(--stage-color) 45%, var(--tblr-dark)); + background: color-mix(in srgb, var(--stage-color) 45%, var(--tblr-body-bg)); padding: 10px 0; text-align: center; color: var(--tblr-secondary-color); @@ -228,7 +228,7 @@ pointer-events: none; } .timeline-fill { - background: color-mix(in srgb, var(--stage-color) 45%, var(--tblr-dark)); + background: color-mix(in srgb, var(--stage-color) 45%, var(--tblr-body-bg)); } .stream-timeline { position: relative; diff --git a/examples/official-site/assets/landing/styles/hero.css b/examples/official-site/assets/landing/styles/hero.css index a2d19521..b9b3240d 100644 --- a/examples/official-site/assets/landing/styles/hero.css +++ b/examples/official-site/assets/landing/styles/hero.css @@ -26,8 +26,8 @@ linear-gradient( 135deg, color-mix(in srgb, var(--tblr-body-bg), var(--tblr-black) 70%), - var(--tblr-dark) 72%, - var(--tblr-dark) + var(--tblr-body-bg) 72%, + var(--tblr-body-bg) ); box-shadow: inset 0 5px 9px color-mix(in srgb, var(--tblr-black) 73.33%, transparent), diff --git a/examples/official-site/assets/landing/styles/interaction.css b/examples/official-site/assets/landing/styles/interaction.css index 76c5f7ab..8af27d96 100644 --- a/examples/official-site/assets/landing/styles/interaction.css +++ b/examples/official-site/assets/landing/styles/interaction.css @@ -53,7 +53,7 @@ color: var(--tblr-blue); background: var( --tblr-success-lt, - color-mix(in srgb, var(--tblr-success) 20%, var(--tblr-dark)) + color-mix(in srgb, var(--tblr-success) 20%, var(--tblr-body-bg)) ); padding: 8px 17px; border-radius: 50px; diff --git a/examples/official-site/assets/landing/styles/responsive.css b/examples/official-site/assets/landing/styles/responsive.css index 74bc06a0..8ea2eb25 100644 --- a/examples/official-site/assets/landing/styles/responsive.css +++ b/examples/official-site/assets/landing/styles/responsive.css @@ -290,7 +290,7 @@ background: linear-gradient( 150deg, color-mix(in srgb, var(--tblr-link-hover-color) 7.84%, transparent), - color-mix(in srgb, var(--tblr-dark) 33.33%, transparent) + color-mix(in srgb, var(--tblr-body-bg) 33.33%, transparent) ); box-shadow: inset 0 1px 1px color-mix(in srgb, var(--tblr-light) 12.94%, transparent), diff --git a/examples/official-site/assets/landing/styles/ribbon.css b/examples/official-site/assets/landing/styles/ribbon.css index 93936b45..a3b184a9 100644 --- a/examples/official-site/assets/landing/styles/ribbon.css +++ b/examples/official-site/assets/landing/styles/ribbon.css @@ -15,8 +15,8 @@ background: linear-gradient( 180deg, color-mix(in srgb, var(--tblr-bg-surface-secondary) 78.04%, transparent), - color-mix(in srgb, var(--tblr-dark) 92.94%, transparent) 45%, - color-mix(in srgb, var(--tblr-dark) 96.08%, transparent) + color-mix(in srgb, var(--tblr-body-bg) 92.94%, transparent) 45%, + color-mix(in srgb, var(--tblr-body-bg) 96.08%, transparent) ); opacity: var(--ribbon-opacity); overflow: hidden; diff --git a/examples/official-site/assets/landing/styles/section-motion.css b/examples/official-site/assets/landing/styles/section-motion.css index b9b46d77..b3008c71 100644 --- a/examples/official-site/assets/landing/styles/section-motion.css +++ b/examples/official-site/assets/landing/styles/section-motion.css @@ -16,7 +16,7 @@ position: absolute; inset: 0; z-index: 2; - background: var(--tblr-dark); + background: var(--tblr-body-bg); opacity: calc(var(--section-cover, 0) * 0.16); pointer-events: none; } diff --git a/examples/official-site/assets/landing/styles/sections.css b/examples/official-site/assets/landing/styles/sections.css index 8a26c734..be3ed6ae 100644 --- a/examples/official-site/assets/landing/styles/sections.css +++ b/examples/official-site/assets/landing/styles/sections.css @@ -1,6 +1,6 @@ /* Section composition and shared content primitives; section-motion.css owns scroll poses. */ .landing-section { - --section-color: var(--tblr-dark); + --section-color: var(--tblr-body-bg); position: relative; margin-top: -1px; padding: 110px 5.5% 100px; @@ -114,7 +114,7 @@ .segmented button { border: 0; border-radius: 7px; - background: var(--tblr-dark); + background: var(--tblr-body-bg); color: var(--tblr-body-color); padding: 12px 16px; font-weight: 600; @@ -205,7 +205,7 @@ border: 1px solid var(--tblr-border-color); border-radius: 15px; padding: 24px; - background: var(--tblr-dark); + background: var(--tblr-body-bg); transition: border-color 0.2s, transform 0.2s; @@ -271,7 +271,7 @@ a.chip:hover { .database-grid a { border: 1px solid var(--tblr-blue); border-radius: 12px; - background: var(--tblr-dark); + background: var(--tblr-body-bg); color: var(--tblr-blue); padding: 28px 22px; font: 24px var(--tblr-font-monospace); @@ -308,7 +308,7 @@ a.chip:hover { height: 12px; } .commit-section { - background: var(--tblr-dark); + background: var(--tblr-body-bg); display: flex; } /* Keep the footer at the end of the final cover, even on tall screens. */ From ade8d0514ffdc58626c91e8b40b498355f41f132 Mon Sep 17 00:00:00 2001 From: O L Date: Mon, 26 Oct 2026 21:01:31 +0000 Subject: [PATCH 8/8] Track carousel destinations across rapid smooth-scroll navigation --- .../assets/landing/js/comparison.js | 30 +++++++++++++++---- tests/end-to-end/landing-comparison.spec.ts | 16 ++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/examples/official-site/assets/landing/js/comparison.js b/examples/official-site/assets/landing/js/comparison.js index 507037d8..f1135526 100644 --- a/examples/official-site/assets/landing/js/comparison.js +++ b/examples/official-site/assets/landing/js/comparison.js @@ -7,6 +7,7 @@ export function initComparison(root, signal) { const next = section.querySelector("[data-comparison-next]"); const status = section.querySelector("[data-comparison-status]"); const motion = matchMedia("(prefers-reduced-motion: reduce)"); + let destination = null; function update() { const offsets = cards.map((card) => card.offsetLeft); @@ -27,8 +28,12 @@ export function initComparison(root, signal) { } function goTo(left) { + destination = Math.max( + 0, + Math.min(left, track.scrollWidth - track.clientWidth), + ); track.scrollTo({ - left, + left: destination, behavior: motion.matches ? "instant" : "smooth", }); } @@ -36,16 +41,28 @@ export function initComparison(root, signal) { function move(direction) { // Equal-width grid columns snap one card at a time; native scrolling clamps the ends. const step = cards[1].offsetLeft; + // Repeated clicks advance from the requested card, even while a previous + // smooth scroll is still exposing a partial card and updating the status. const index = - direction > 0 - ? Math.floor((track.scrollLeft + 2) / step) + 1 - : Math.ceil((track.scrollLeft - 2) / step) - 1; + Math.round((destination ?? track.scrollLeft) / step) + direction; goTo(index * step); } previous.addEventListener("click", () => move(-1), { signal }); next.addEventListener("click", () => move(1), { signal }); track.addEventListener("scroll", update, { passive: true, signal }); + const followNativeScroll = () => { + destination = null; + }; + track.addEventListener("scrollend", followNativeScroll, { signal }); + track.addEventListener("pointerdown", followNativeScroll, { + passive: true, + signal, + }); + track.addEventListener("wheel", followNativeScroll, { + passive: true, + signal, + }); const keys = { ArrowLeft: () => move(-1), ArrowRight: () => move(1), @@ -61,7 +78,10 @@ export function initComparison(root, signal) { }, { signal }, ); - const observer = new ResizeObserver(update); + const observer = new ResizeObserver(() => { + destination = null; + update(); + }); observer.observe(track); section.querySelector(".comparison-controls").hidden = false; update(); diff --git a/tests/end-to-end/landing-comparison.spec.ts b/tests/end-to-end/landing-comparison.spec.ts index 8aecd0af..55bd1d1c 100644 --- a/tests/end-to-end/landing-comparison.spec.ts +++ b/tests/end-to-end/landing-comparison.spec.ts @@ -30,6 +30,22 @@ for (const settings of [ const initialHeight = await section.evaluate( (section) => section.clientHeight, ); + if (settings.reducedMotion === "no-preference") { + // Click again before the previous smooth scroll reaches its destination. + await next.evaluate((button) => { + if (!(button instanceof HTMLButtonElement)) + throw new Error("Missing next button"); + button.click(); + button.click(); + button.click(); + }); + await expect(status).toHaveText("4–5 of 5 alternatives"); + await expect(next).toBeDisabled(); + await track.focus(); + await track.press("Home"); + await expect(previous).toBeDisabled(); + await expect(status).toHaveText("1–2 of 5 alternatives"); + } for (let index = 1; index <= 5 - settings.visible; index++) { await next.click(); await expect(status).toHaveText(