mirror of
https://github.com/lucide-icons/lucide.git
synced 2026-11-15 14:08:49 +00:00
feat(site): Add Hover And Focus Animations Across The Docs Site (#5085)
* feat(docs): Add Hover And Focus Animations Across The Docs Site - Nav bar and footer links: hover underline that draws in from the side the mouse enters and leaves (utils/underlineDirection.ts) - Logo spins half a turn on hover; nav dropdown chevron turns and the menu slides in; social icons get a soft background and lift - Sidebar, "On this page" outline, "Edit this page" and previous/next links get soft pill hovers; previous/next use Lucide arrow icons - Footer is now shown on pages with a sidebar, offset by the sidebar width - Form controls (switch, checkbox, select, range slider, color picker, fake input, reset button) get brand hover/focus states and smoother motion - Cards (overview, package, showcase, team member, category list) lift with a brand border on hover - Page and sidebar scrollbars styled in the site colours, with a Firefox fallback * Revert changes * Apply changes * Added the global transition off * fix(docs): remove !important markers from transition durations, very bad a11y * fix(docs): remove !important markers from transition durations, very bad a11y * Fixes last things * Added one thing --------- Co-authored-by: Eric <eric.fennis@gmail.com> Co-authored-by: Karsa <contact@karsa.org>
This commit is contained in:
co-authored by
Eric
Karsa
parent
5abef78c8e
commit
6e25d21b2f
@@ -1,7 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "Eric Fennis",
|
||||
"title": "Creator of Lucide & Software engineer @nedap",
|
||||
"title": "Creator & Maintainer of Lucide",
|
||||
"image": "https://github.com/ericfennis.png?size=192",
|
||||
"sponsor": "https://github.com/sponsors/ericfennis",
|
||||
"socialLinks": [
|
||||
@@ -12,6 +12,10 @@
|
||||
{
|
||||
"icon": "x",
|
||||
"link": "https://x.com/ericfennis"
|
||||
},
|
||||
{
|
||||
"icon": "linkedin",
|
||||
"link": "https://www.linkedin.com/in/ericfennis"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -32,7 +36,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Jakob Guddas",
|
||||
"title": "Maintainer of Lucide & Software engineer @LEGO",
|
||||
"title": "Creator of Lucide Studio",
|
||||
"image": "https://github.com/jguddas.png?size=192",
|
||||
"socialLinks": [
|
||||
{
|
||||
|
||||
@@ -31,7 +31,7 @@ const rel = computed(() => {
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
border: 1px solid var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
display: flex;
|
||||
@@ -52,5 +52,4 @@ const rel = computed(() => {
|
||||
.card[href]:hover {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -79,6 +79,15 @@ const model = computed({
|
||||
border: 1px solid var(--vp-input-border-color);
|
||||
background-color: var(--vp-input-switch-bg-color);
|
||||
border-radius: 4px;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
.checkbox:hover,
|
||||
.checkbox:focus-visible {
|
||||
outline: none;
|
||||
border-color: var(--vp-c-brand);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--vp-c-brand) 20%, transparent);
|
||||
}
|
||||
|
||||
.checkbox:checked {
|
||||
@@ -87,4 +96,8 @@ const model = computed({
|
||||
center no-repeat var(--vp-c-brand);;
|
||||
}
|
||||
|
||||
.checkbox-wrapper:hover .checkbox-label {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -35,19 +35,37 @@ defineProps({
|
||||
font-size: 14px;
|
||||
/* box-shadow: var(--vp-shadow-4), var(--vp-shadow-2); */
|
||||
text-align: left;
|
||||
border: 1px solid transparent;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
cursor: text;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
transition:
|
||||
color 0.25s,
|
||||
border-color 0.25s,
|
||||
background-color 0.25s;
|
||||
background-color 0.25s,
|
||||
box-shadow 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
|
||||
/* A quiet border at rest; on hover the border takes the brand colour with a soft ring,
|
||||
and the search icon tilts as if it starts looking */
|
||||
.fake-input:hover,
|
||||
.fake-input:focus {
|
||||
.fake-input:focus-visible {
|
||||
outline: none;
|
||||
color: var(--vp-c-text-1);
|
||||
border-color: var(--vp-c-brand);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--vp-c-brand) 15%, transparent);
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
flex-shrink: 0;
|
||||
transition: color 0.25s, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
transition-duration: 0.25s, 0.3s;
|
||||
}
|
||||
|
||||
.fake-input:hover .search-icon,
|
||||
.fake-input:focus-visible .search-icon {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.shortcut {
|
||||
|
||||
@@ -99,7 +99,7 @@ defineExpose({
|
||||
|
||||
.input {
|
||||
justify-content: flex-start;
|
||||
border: 1px solid transparent;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 0 10px 0 12px;
|
||||
width: 100%;
|
||||
@@ -109,16 +109,21 @@ defineExpose({
|
||||
transition:
|
||||
color 0.25s,
|
||||
border-color 0.25s,
|
||||
background-color 0.25s;
|
||||
background-color 0.25s,
|
||||
box-shadow 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
|
||||
.input.has-shortcut {
|
||||
padding-right: calc(var(--shortcut-width, 40px) + 22px);
|
||||
}
|
||||
|
||||
/* Same as FakeInput: a quiet border at rest, a brand border with a soft ring on hover and focus */
|
||||
.input:hover,
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: var(--vp-c-brand);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--vp-c-brand) 15%, transparent);
|
||||
}
|
||||
.input:focus {
|
||||
background-color: var(--vp-c-bg);
|
||||
@@ -190,8 +195,13 @@ defineExpose({
|
||||
z-index: 1;
|
||||
color: var(--vp-c-text-2);
|
||||
pointer-events: none;
|
||||
transition: color 0.25s, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
transition-duration: 0.25s, 0.3s;
|
||||
}
|
||||
|
||||
/* Like the FakeInput search icon, it tilts as if it starts looking */
|
||||
.input-wrapper > svg:has(+ .input.has-icon:hover),
|
||||
.input-wrapper > svg:has(+ .input.has-icon:focus) {
|
||||
color: var(--vp-c-text-1);
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -33,12 +33,45 @@ defineProps<{
|
||||
padding: 11px 16px 13px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: border-color 0.25s;
|
||||
transition: border-color 0.25s, background-color 0.25s, transform 0.25s ease, box-shadow 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.overview-link:hover {
|
||||
/* On hover the card lifts with a light brand tint, like the previous / next page links,
|
||||
and Lucide's arrow-right icon slides in after the title */
|
||||
.overview-link:hover,
|
||||
.overview-link:focus-visible {
|
||||
outline: none;
|
||||
border-color: var(--vp-c-brand-1);
|
||||
transform: translateY(-2px);
|
||||
background-color: color-mix(in srgb, var(--vp-c-brand) 6%, transparent);
|
||||
box-shadow: var(--vp-shadow-2);
|
||||
}
|
||||
|
||||
.overview-link .title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.overview-link .title::after {
|
||||
content: '';
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: currentColor;
|
||||
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3Cpath d='m12 5 7 7-7 7'/%3E%3C/svg%3E") center / contain no-repeat;
|
||||
opacity: 0;
|
||||
transform: translateX(-6px);
|
||||
transition: opacity 0.25s, transform 0.25s ease;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
|
||||
.overview-link:hover .title::after,
|
||||
.overview-link:focus-visible .title::after {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.desc {
|
||||
|
||||
@@ -111,6 +111,36 @@ const percentage = computed<string>(() => `${((Number(props.modelValue) - props.
|
||||
border-radius: 10px;
|
||||
box-shadow: var(--vp-shadow-2);
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
.slider input::-moz-range-thumb {
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
/* The thumb grows with a soft brand ring on hover and focus, and a little more while dragged */
|
||||
.slider input:hover::-webkit-slider-thumb,
|
||||
.slider input:focus-visible::-webkit-slider-thumb {
|
||||
transform: scale(1.1);
|
||||
box-shadow: var(--vp-shadow-2), 0 0 0 5px color-mix(in srgb, var(--vp-c-brand) 25%, transparent);
|
||||
}
|
||||
|
||||
.slider input:active::-webkit-slider-thumb {
|
||||
transform: scale(1.2);
|
||||
box-shadow: var(--vp-shadow-2), 0 0 0 7px color-mix(in srgb, var(--vp-c-brand) 25%, transparent);
|
||||
}
|
||||
|
||||
.slider input:hover::-moz-range-thumb,
|
||||
.slider input:focus-visible::-moz-range-thumb {
|
||||
transform: scale(1.1);
|
||||
box-shadow: var(--vp-shadow-2), 0 0 0 5px color-mix(in srgb, var(--vp-c-brand) 25%, transparent);
|
||||
}
|
||||
|
||||
.slider input:active::-moz-range-thumb {
|
||||
transform: scale(1.2);
|
||||
box-shadow: var(--vp-shadow-2), 0 0 0 7px color-mix(in srgb, var(--vp-c-brand) 25%, transparent);
|
||||
}
|
||||
|
||||
.slider input::-moz-range-thumb {
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { rotateCw } from '../../../data/iconNodes';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import Button from './Button.vue';
|
||||
|
||||
const spinning = ref(false);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button class="reset-button" aria-label="Reset style">
|
||||
<Button
|
||||
class="reset-button"
|
||||
:class="{ spinning }"
|
||||
aria-label="Reset style"
|
||||
@click="spinning = true"
|
||||
@animationend="spinning = false"
|
||||
>
|
||||
<Icon
|
||||
:size="20"
|
||||
:iconNode="rotateCw"
|
||||
@@ -16,13 +25,16 @@ import Button from './Button.vue';
|
||||
|
||||
<style scoped>
|
||||
.reset-button {
|
||||
transition: ease-in-out 0.1s opacity;
|
||||
transition: ease-in-out 0.25s opacity;
|
||||
transition-duration: 0.25s;
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.reset-button .lucide {
|
||||
transition: ease-in-out 0.1s transform;
|
||||
/* The arrow spins once in place when clicked */
|
||||
.reset-button.spinning .lucide {
|
||||
animation: rotate 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
animation-duration: 0.6s !important;
|
||||
}
|
||||
|
||||
.reset-button:hover {
|
||||
@@ -36,18 +48,13 @@ import Button from './Button.vue';
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* a rotate css animation keyframes */
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(359deg);
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.reset-button:active .lucide {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -36,6 +36,8 @@ const selected = defineModel<{ name: string; value: string; icon?: string; iconD
|
||||
</ListboxButton>
|
||||
|
||||
<transition
|
||||
enter-active-class="transition-enter"
|
||||
enter-from-class="transition-enter-from"
|
||||
leave-active-class="transition-leave"
|
||||
leave-from-class="transition-leave-from"
|
||||
leave-to-class="transition-leave-to"
|
||||
@@ -83,20 +85,33 @@ const selected = defineModel<{ name: string; value: string; icon?: string; iconD
|
||||
padding: 7px 14px;
|
||||
height: auto;
|
||||
font-size: 14px;
|
||||
border: 1px solid transparent;
|
||||
cursor: text;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
transition:
|
||||
color 0.25s,
|
||||
border-color 0.25s,
|
||||
background-color 0.25s;
|
||||
background-color 0.25s,
|
||||
box-shadow 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.select-button:focus {
|
||||
/* Same as FakeInput: a quiet border at rest, a brand border with a soft ring on hover, focus
|
||||
and while open, and the chevrons take the text colour */
|
||||
.select-button:hover,
|
||||
.select-button:focus,
|
||||
.select-button[aria-expanded='true'] {
|
||||
outline: none;
|
||||
border-color: var(--vp-c-brand);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--vp-c-brand) 15%, transparent);
|
||||
}
|
||||
|
||||
.select-button:hover .chevron-icon,
|
||||
.select-button[aria-expanded='true'] .chevron-icon {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.select-text {
|
||||
@@ -121,10 +136,26 @@ const selected = defineModel<{ name: string; value: string; icon?: string; iconD
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
color: #9ca3af;
|
||||
transition: color 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
|
||||
/* The list fades in and slides down a little from the button */
|
||||
.transition-enter {
|
||||
transition:
|
||||
opacity 150ms ease-out,
|
||||
transform 200ms cubic-bezier(0.34, 1.4, 0.64, 1);
|
||||
transition-duration: 150ms, 200ms;
|
||||
}
|
||||
|
||||
.transition-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(-6px) scale(0.98);
|
||||
}
|
||||
|
||||
.transition-leave {
|
||||
transition: opacity 100ms ease-in;
|
||||
transition-duration: 100ms;
|
||||
}
|
||||
|
||||
.transition-leave-from {
|
||||
@@ -172,6 +203,7 @@ const selected = defineModel<{ name: string; value: string; icon?: string; iconD
|
||||
transition:
|
||||
background-color 0.25s,
|
||||
color 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
border: 1px solid var(--vp-input-border-color);
|
||||
background-color: var(--vp-input-switch-bg-color);
|
||||
transition: border-color 0.25s, background-color 0.4s ease;
|
||||
transition-duration: 0.25s, 0.4s;
|
||||
border-radius: 11px;
|
||||
}
|
||||
|
||||
@@ -56,21 +57,51 @@
|
||||
border-color: var(--vp-input-hover-border-color);
|
||||
}
|
||||
|
||||
.switch:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* The thumb sits 2px inside the track and slides with a small bounce. Like the RangeSlider
|
||||
thumb, it grows with a soft brand ring on hover and focus, and a little more while pressed */
|
||||
.thumb {
|
||||
display: inline-block;
|
||||
margin: 2px;
|
||||
background-color: #fff;
|
||||
transition: transform 0.25s;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
transition:
|
||||
transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1),
|
||||
box-shadow 0.2s ease;
|
||||
transition-duration: 0.4s, 0.2s;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 8px;
|
||||
/* background-color: var(--vp-c-neutral); */
|
||||
box-shadow: var(--vp-shadow-1);
|
||||
}
|
||||
|
||||
.switch:hover .thumb,
|
||||
.switch:focus-visible .thumb {
|
||||
transform: scale(1.1);
|
||||
box-shadow: var(--vp-shadow-1), 0 0 0 5px color-mix(in srgb, var(--vp-c-brand) 25%, transparent);
|
||||
}
|
||||
|
||||
.switch:active .thumb {
|
||||
transform: scale(1.2);
|
||||
box-shadow: var(--vp-shadow-1), 0 0 0 7px color-mix(in srgb, var(--vp-c-brand) 25%, transparent);
|
||||
}
|
||||
|
||||
.switch.enabled .thumb {
|
||||
transform: translateX(18px);
|
||||
}
|
||||
|
||||
.switch.enabled:hover .thumb,
|
||||
.switch.enabled:focus-visible .thumb {
|
||||
transform: translateX(18px) scale(1.1);
|
||||
}
|
||||
|
||||
.switch.enabled:active .thumb {
|
||||
transform: translateX(18px) scale(1.2);
|
||||
}
|
||||
|
||||
/* ------------------- */
|
||||
.VPSwitch {
|
||||
position: relative;
|
||||
|
||||
@@ -8,6 +8,7 @@ import ColorPicker from '../base/ColorPicker.vue';
|
||||
import ResetButton from '../base/ResetButton.vue';
|
||||
import HomeIconCustomizerIcons from './HomeIconCustomizerIcons.vue';
|
||||
import Switch from '../base/Switch.vue';
|
||||
import Card from '../base/Card.vue';
|
||||
import useTrackStyleChanges from '../../composables/useTrackStyleChanges';
|
||||
import { track } from '../../utils/openpanel';
|
||||
|
||||
@@ -31,7 +32,7 @@ const iconStyle = computed(() => ({
|
||||
|
||||
<template>
|
||||
<HomeContainer>
|
||||
<div class="card">
|
||||
<Card class="customizer-card">
|
||||
<div class="card-column">
|
||||
<h2 class="title">
|
||||
Style as you please
|
||||
@@ -116,16 +117,14 @@ const iconStyle = computed(() => ({
|
||||
:style="iconStyle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</HomeContainer>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
display: block;
|
||||
border-radius: 12px;
|
||||
.customizer-card {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 24px;
|
||||
--slider-bar-color: var(--vp-c-bg-soft-down);
|
||||
--color-picker-bg: var(--vp-c-bg-soft-down);
|
||||
|
||||
@@ -10,9 +10,10 @@ export interface TeamMember {
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DefaultTheme } from 'vitepress';
|
||||
import Icon from '@lucide/vue/src/Icon'
|
||||
import Card from '../base/Card.vue'
|
||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'
|
||||
import VPSocialLinks from 'vitepress/dist/client/theme-default/components/VPSocialLinks.vue'
|
||||
import { heart } from '../../../data/iconNodes'
|
||||
|
||||
defineProps<TeamMember>()
|
||||
</script>
|
||||
@@ -20,7 +21,9 @@ defineProps<TeamMember>()
|
||||
<template>
|
||||
<div>
|
||||
<Card class="member-card">
|
||||
<a :href="socialLinks[0]?.link" target="_blank" rel="noreferrer noopener">
|
||||
<img :src="image" :alt="name" class="member-image"/>
|
||||
</a>
|
||||
<h3 class="member-name">
|
||||
{{name}}
|
||||
</h3>
|
||||
@@ -28,16 +31,25 @@ defineProps<TeamMember>()
|
||||
{{title}}
|
||||
</p>
|
||||
<div class="member-links">
|
||||
<VPButton
|
||||
<a
|
||||
v-if="sponsor"
|
||||
:href="sponsor"
|
||||
text="Sponsor"
|
||||
theme="sponsor"
|
||||
class="sponsor-button"
|
||||
size="medium"
|
||||
/>
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<Icon
|
||||
:iconNode="heart"
|
||||
name="heart"
|
||||
:size="16"
|
||||
class="sponsor-heart"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Sponsor
|
||||
</a>
|
||||
<VPSocialLinks
|
||||
:links="socialLinks"
|
||||
class="member-social-links"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -57,6 +69,14 @@ defineProps<TeamMember>()
|
||||
border-radius: 32px;
|
||||
margin: 0 auto;
|
||||
background-color: var(--vp-c-bg);
|
||||
box-shadow: 0 0 0 0 transparent;
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
transition-duration: 0.3s;
|
||||
}
|
||||
|
||||
.member-image:hover {
|
||||
transform: scale(1.06);
|
||||
box-shadow: 0 0 0 3px var(--vp-c-bg-soft), 0 0 0 5px var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.member-name {
|
||||
@@ -79,14 +99,81 @@ defineProps<TeamMember>()
|
||||
margin-bottom: 16px;;
|
||||
}
|
||||
|
||||
.sponsor-button {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.member-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.sponsor-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 36px;
|
||||
padding: 0 16px 0 14px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-2);
|
||||
text-decoration: none;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 18px;
|
||||
background-color: var(--vp-c-bg);
|
||||
transition: color 0.25s, background-color 0.25s, border-color 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
|
||||
.sponsor-heart {
|
||||
color: var(--vp-c-sponsor);
|
||||
transition: color 0.25s, fill 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
|
||||
.sponsor-button:hover,
|
||||
.sponsor-button:focus-visible {
|
||||
outline: none;
|
||||
color: #fff;
|
||||
border-color: var(--vp-c-sponsor);
|
||||
background-color: var(--vp-c-sponsor);
|
||||
}
|
||||
|
||||
.sponsor-button:hover .sponsor-heart,
|
||||
.sponsor-button:focus-visible .sponsor-heart {
|
||||
color: #fff;
|
||||
fill: #fff;
|
||||
animation: heartbeat 1s ease-in-out infinite;
|
||||
animation-duration: 1s !important;
|
||||
animation-iteration-count: infinite !important;
|
||||
}
|
||||
|
||||
@keyframes heartbeat {
|
||||
0%,
|
||||
40%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
20% {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
}
|
||||
|
||||
/* Social icons get a soft rounded background and lift a little on hover */
|
||||
.member-social-links :deep(.VPSocialLink) {
|
||||
border-radius: 8px;
|
||||
transition: color 0.25s, background-color 0.25s, transform 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
|
||||
.member-social-links :deep(.VPSocialLink:hover),
|
||||
.member-social-links :deep(.VPSocialLink:focus-visible) {
|
||||
outline: none;
|
||||
color: var(--vp-c-text-1);
|
||||
background-color: var(--vp-c-default-soft);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -74,7 +74,8 @@ watch(headers, () => {
|
||||
line-height: 24px;
|
||||
font-size: 14px;
|
||||
display: block;
|
||||
transition: color 0.25s;
|
||||
transition: color 0.25s, transform 0.25s ease;
|
||||
transition-duration: 0.25s;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
@@ -86,6 +87,13 @@ watch(headers, () => {
|
||||
.sidebar-link:hover, .sidebar-link.active {
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
/* All and Categories slide in a little on hover */
|
||||
.sidebar-link:hover,
|
||||
.sidebar-link:focus-visible {
|
||||
outline: none;
|
||||
transform: translateX(4px);
|
||||
}
|
||||
.content {
|
||||
margin-top: 12px;
|
||||
position: relative;
|
||||
@@ -101,8 +109,9 @@ watch(headers, () => {
|
||||
left: -1px;
|
||||
z-index: 0;
|
||||
opacity: 0;
|
||||
width: 1px;
|
||||
width: 2px;
|
||||
height: 18px;
|
||||
border-radius: 1px;
|
||||
background-color: var(--vp-c-brand);
|
||||
transition: top 0.25s cubic-bezier(0, 1, 0.5, 1), background-color 0.5s, opacity 0.25s;
|
||||
}
|
||||
|
||||
@@ -68,22 +68,45 @@ function onClick(categoryName: string) {
|
||||
padding-left: 13px;
|
||||
}
|
||||
|
||||
/* Each category is a soft pill on hover: the name slides in a little and the count badge
|
||||
takes the brand colour. The category in view keeps the brand colour. */
|
||||
.outline-link {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
line-height: 28px;
|
||||
align-items: center;
|
||||
margin: 1px -8px;
|
||||
padding: 0 6px 0 8px;
|
||||
line-height: 26px;
|
||||
color: var(--vp-c-text-2);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: color 0.5s;
|
||||
border-radius: 6px;
|
||||
transition:
|
||||
color 0.25s,
|
||||
background-color 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.outline-link > span:first-child {
|
||||
transition: transform 0.25s ease;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
|
||||
.outline-link:hover,
|
||||
.outline-link.active {
|
||||
.outline-link:focus-visible {
|
||||
outline: none;
|
||||
color: var(--vp-c-text-1);
|
||||
transition: color 0.25s;
|
||||
background-color: var(--vp-c-default-soft);
|
||||
}
|
||||
|
||||
.outline-link:hover > span:first-child,
|
||||
.outline-link:focus-visible > span:first-child {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
.outline-link.active {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.outline-link.inactive {
|
||||
@@ -96,10 +119,26 @@ function onClick(categoryName: string) {
|
||||
}
|
||||
|
||||
.icon-count {
|
||||
opacity: 0.5;
|
||||
margin-left: auto;
|
||||
padding: 0 6px;
|
||||
min-width: 26px;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-3);
|
||||
border-radius: 9px;
|
||||
transition:
|
||||
color 0.25s,
|
||||
background-color 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
|
||||
.outline-link:hover .icon-count,
|
||||
.outline-link:focus-visible .icon-count,
|
||||
.outline-link.active .icon-count {
|
||||
color: var(--vp-c-brand-1);
|
||||
background-color: color-mix(in srgb, var(--vp-c-brand) 14%, transparent);
|
||||
}
|
||||
|
||||
.outline-link.inactive .icon-count {
|
||||
|
||||
@@ -157,6 +157,8 @@ const Expand = createLucideIcon('Expand', expand);
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
background-color: var(--vp-c-bg-elv);
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
border-right: 1px solid var(--vp-c-divider);
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||||
will-change: transform;
|
||||
pointer-events: all;
|
||||
|
||||
@@ -7,6 +7,7 @@ import InputField from '../base/InputField.vue';
|
||||
import ColorPicker from '../base/ColorPicker.vue';
|
||||
import ResetButton from '../base/ResetButton.vue';
|
||||
import Switch from '../base/Switch.vue';
|
||||
import Card from '../base/Card.vue';
|
||||
import useTrackStyleChanges from '../../composables/useTrackStyleChanges';
|
||||
import { track } from '../../utils/openpanel';
|
||||
|
||||
@@ -29,7 +30,7 @@ function onReset() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
<Card
|
||||
class="customizer-card"
|
||||
:class="{ customized: showCustomized }"
|
||||
>
|
||||
@@ -95,7 +96,7 @@ function onReset() {
|
||||
v-model="nonScalingStroke"
|
||||
/>
|
||||
</InputField>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -113,15 +114,13 @@ function onReset() {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.customizer-card {
|
||||
background: var(--vp-c-bg);
|
||||
.card.customizer-card {
|
||||
padding: 12px 24px 24px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 24px;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
border: 1px solid transparent;
|
||||
transition: border-color 0.4s ease-in-out;
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.customizer-card.customized {
|
||||
|
||||
@@ -64,13 +64,14 @@ if (carbonOptions) {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
padding: 16px 20px 20px;
|
||||
border-radius: 12px;
|
||||
min-height: 256px;
|
||||
text-align: center;
|
||||
line-height: 18px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background-color: var(--vp-carbon-ads-bg-color);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,21 +26,90 @@ defineProps<{
|
||||
class="logo dark"
|
||||
:alt="`${showcaseItem.name} logo`"
|
||||
/>
|
||||
<span
|
||||
class="company-name"
|
||||
aria-hidden="true"
|
||||
>{{ showcaseItem.name }}</span
|
||||
>
|
||||
<span
|
||||
class="company-arrow"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.company {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding: 48px 0;
|
||||
}
|
||||
|
||||
/* On hover the card lifts with a brand border, the logo grows and moves up a little to make
|
||||
room for the name sliding in below it, and an arrow shows the link opens the site */
|
||||
.company:hover,
|
||||
.company:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height:64px;
|
||||
width: 240px;
|
||||
margin: auto;
|
||||
opacity: 0.8;
|
||||
transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s;
|
||||
transition-duration: 0.4s, 0.3s;
|
||||
}
|
||||
|
||||
.company:hover .logo,
|
||||
.company:focus-visible .logo {
|
||||
opacity: 1;
|
||||
transform: translateY(-8px) scale(1.02);
|
||||
}
|
||||
|
||||
.company-name {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 14px;
|
||||
left: 0;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-2);
|
||||
opacity: 0;
|
||||
transform: translateY(3px);
|
||||
transition: opacity 0.3s, transform 0.3s ease;
|
||||
transition-duration: 0.3s;
|
||||
}
|
||||
|
||||
.company:hover .company-name,
|
||||
.company:focus-visible .company-name {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Lucide's arrow-up-right icon */
|
||||
.company-arrow {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
right: 14px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: var(--vp-c-brand-1);
|
||||
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 7h10v10'/%3E%3Cpath d='M7 17 17 7'/%3E%3C/svg%3E") center / contain no-repeat;
|
||||
opacity: 0;
|
||||
transform: translate(-6px, 6px);
|
||||
transition: opacity 0.3s, transform 0.3s ease;
|
||||
transition-duration: 0.3s;
|
||||
}
|
||||
|
||||
.company:hover .company-arrow,
|
||||
.company:focus-visible .company-arrow {
|
||||
opacity: 1;
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
|
||||
html.dark .logo.dark {
|
||||
|
||||
@@ -13,6 +13,7 @@ import { EXTERNAL_LIBS_CONTEXT, externalLibContext } from './composables/useExte
|
||||
import SideBarNavBefore from './layouts/SideBarNavBefore.vue';
|
||||
import SnackPlayer from './components/editors/SnackPlayer.vue';
|
||||
import { setupTracking } from './utils/openpanel';
|
||||
import trackUnderlineDirection from './utils/underlineDirection';
|
||||
|
||||
const theme: Partial<Theme> = {
|
||||
extends: DefaultTheme,
|
||||
@@ -34,6 +35,10 @@ const theme: Partial<Theme> = {
|
||||
app.component('SnackPlayer', SnackPlayer);
|
||||
|
||||
setupTracking(router);
|
||||
|
||||
if (!import.meta.env.SSR) {
|
||||
trackUnderlineDirection();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
+384
-10
@@ -62,6 +62,23 @@
|
||||
navigation: auto;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
|
||||
::view-transition-group(*),
|
||||
::view-transition-old(*),
|
||||
::view-transition-new(*) {
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ::view-transition-old(icons-search-box),
|
||||
::view-transition-new(icons-search-box) {
|
||||
animation: .3s transition 0s ease;
|
||||
@@ -71,6 +88,40 @@ html {
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
/* Page scrollbar in the site's colours: a slim rounded thumb on the page background */
|
||||
html::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar-track,
|
||||
html::-webkit-scrollbar-corner {
|
||||
background-color: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar-thumb {
|
||||
background-color: var(--vp-c-text-3);
|
||||
background-clip: padding-box;
|
||||
border: 3px solid transparent;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar-thumb:active {
|
||||
background-color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
/* Browsers without the scrollbar pseudo-elements, such as Firefox */
|
||||
@supports not selector(::-webkit-scrollbar) {
|
||||
html {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--vp-c-text-3) var(--vp-c-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.VPHome {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
@@ -108,6 +159,10 @@ html {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.VPFeature {
|
||||
border: 1px solid var(--vp-c-divider) !important;
|
||||
}
|
||||
|
||||
.VPFeature .icon {
|
||||
background-color: var(--vp-c-bg);
|
||||
}
|
||||
@@ -166,21 +221,321 @@ html {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Nav bar links: on hover an underline draws in from the side the mouse enters and leaves
|
||||
towards the side it exits (see utils/underlineDirection.ts). Without that script it
|
||||
draws in from the left and leaves to the right. It stays under the link of the current page. */
|
||||
.VPNav .VPNavBarMenuLink,
|
||||
.VPNav .VPNavBarMenuGroup .button {
|
||||
position: relative;
|
||||
transition: opacity 0.25s;
|
||||
}
|
||||
|
||||
.VPNav .VPNavBarMenuLink::before,
|
||||
.VPNav .VPNavBarMenuGroup .button::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
bottom: 16px;
|
||||
left: 12px;
|
||||
height: 2px;
|
||||
border-radius: 2px;
|
||||
background-color: var(--vp-c-brand-1);
|
||||
transform: scaleX(0);
|
||||
transform-origin: var(--underline-origin, right);
|
||||
transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.VPNav .VPNavBarMenuLink:hover::before,
|
||||
.VPNav .VPNavBarMenuLink:focus-visible::before,
|
||||
.VPNav .VPNavBarMenuLink.active::before,
|
||||
.VPNav .VPNavBarMenuGroup:hover .button::before,
|
||||
.VPNav .VPNavBarMenuGroup .button[aria-expanded='true']::before,
|
||||
.VPNav .VPNavBarMenuGroup.active .button::before {
|
||||
transform: scaleX(1);
|
||||
transform-origin: var(--underline-origin, left);
|
||||
}
|
||||
|
||||
/* On hover the link text dims, like the dropdown buttons do; the current page keeps its brand colour */
|
||||
.VPNav .VPNavBarMenuLink:not(.active):hover {
|
||||
color: var(--vp-c-text-1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* The chevron turns up while the dropdown is open, and the menu slides down into place.
|
||||
VitePress draws the down chevron as a right chevron turned by 90deg. */
|
||||
.VPNav .VPNavBarMenuGroup .text-icon {
|
||||
transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.VPNav .VPNavBarMenuGroup:hover .text-icon,
|
||||
.VPNav .VPNavBarMenuGroup .button[aria-expanded='true'] .text-icon {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.VPNav .VPNavBarMenuGroup .button[aria-expanded='false'] + .menu {
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
|
||||
.VPNav .VPNavBarMenuGroup:hover .button + .menu,
|
||||
.VPNav .VPNavBarMenuGroup .button[aria-expanded='true'] + .menu {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Appearance switch: the thumb sits 2px inside the track and slides over with a small bounce,
|
||||
while the sun and the moon turn and grow as they swap places */
|
||||
.VPNav .VPSwitchAppearance .check {
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
html.dark .VPNav .VPSwitchAppearance .check {
|
||||
transform: translateX(18px);
|
||||
}
|
||||
|
||||
.VPNav .VPSwitchAppearance:hover .check,
|
||||
.VPNav .VPSwitchAppearance:focus-visible .check {
|
||||
box-shadow:
|
||||
var(--vp-shadow-1),
|
||||
0 0 0 3px color-mix(in srgb, var(--vp-c-brand) 25%, transparent);
|
||||
}
|
||||
|
||||
.VPNav .VPSwitchAppearance .icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.VPNav .VPSwitchAppearance .icon .sun,
|
||||
.VPNav .VPSwitchAppearance .icon .moon {
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
transition:
|
||||
opacity 0.3s ease,
|
||||
transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
.VPNav .VPSwitchAppearance .icon .moon,
|
||||
html.dark .VPNav .VPSwitchAppearance .icon .sun {
|
||||
opacity: 0;
|
||||
transform: rotate(-90deg) scale(0.4);
|
||||
}
|
||||
|
||||
.VPNav .VPSwitchAppearance .icon .sun,
|
||||
html.dark .VPNav .VPSwitchAppearance .icon .moon {
|
||||
opacity: 1;
|
||||
transform: rotate(0) scale(1);
|
||||
}
|
||||
|
||||
/* Nav bar social icons get a soft rounded background and lift a little on hover */
|
||||
.VPNav .VPSocialLinks .VPSocialLink {
|
||||
border-radius: 8px;
|
||||
transition:
|
||||
color 0.25s,
|
||||
background-color 0.25s,
|
||||
transform 0.25s;
|
||||
}
|
||||
|
||||
.VPNav .VPSocialLinks .VPSocialLink:hover,
|
||||
.VPNav .VPSocialLinks .VPSocialLink:focus-visible {
|
||||
outline: none;
|
||||
color: var(--vp-c-text-1);
|
||||
background-color: var(--vp-c-default-soft);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.package-lucide-logo {
|
||||
margin: 0 auto 48px;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.VPSidebar {
|
||||
/* Hide Scrollbar */
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
scrollbar-width: thin; /* can also be normal, or none, to not render scrollbar */
|
||||
scrollbar-color: var(--vp-c-text-4) transparent; /* foreground background */
|
||||
/* Sidebar links: a soft pill on hover with the text sliding in a little, a light brand pill
|
||||
for the current page, and a smooth turn of the section chevrons */
|
||||
.VPSidebar .VPSidebarItem.is-link > .item > .link {
|
||||
margin: 1px -8px;
|
||||
padding: 0 8px;
|
||||
border-radius: 6px;
|
||||
transition: background-color 0.25s;
|
||||
}
|
||||
|
||||
.VPSidebar .VPSidebarItem.is-link > .item > .link > .text {
|
||||
transition:
|
||||
color 0.25s,
|
||||
transform 0.25s ease;
|
||||
}
|
||||
|
||||
.VPSidebar .VPSidebarItem.is-link > .item > .link:hover,
|
||||
.VPSidebar .VPSidebarItem.is-link > .item > .link:focus-visible {
|
||||
outline: none;
|
||||
background-color: var(--vp-c-default-soft);
|
||||
}
|
||||
|
||||
.VPSidebar .VPSidebarItem.is-link > .item > .link:hover > .text,
|
||||
.VPSidebar .VPSidebarItem.is-link > .item > .link:focus-visible > .text {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
.VPSidebar .VPSidebarItem.is-active > .item > .link {
|
||||
background-color: color-mix(in srgb, var(--vp-c-brand) 10%, transparent);
|
||||
}
|
||||
|
||||
.VPSidebar .VPSidebarItem .caret-icon {
|
||||
transition: transform 0.3s cubic-bezier(0.34, 1.4, 0.64, 1);
|
||||
}
|
||||
|
||||
/* Links in the page text: a faint underline shows they are links, and on hover a solid
|
||||
underline draws in from the left. Drawn with backgrounds so it also follows links that
|
||||
wrap onto a second line. */
|
||||
.vp-doc a:not([class]) {
|
||||
text-decoration: none;
|
||||
padding-bottom: 1px;
|
||||
background-image:
|
||||
linear-gradient(currentColor, currentColor),
|
||||
linear-gradient(
|
||||
color-mix(in srgb, var(--vp-c-brand) 35%, transparent),
|
||||
color-mix(in srgb, var(--vp-c-brand) 35%, transparent)
|
||||
);
|
||||
background-position:
|
||||
0 100%,
|
||||
0 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-size:
|
||||
0% 2px,
|
||||
100% 1px;
|
||||
transition:
|
||||
color 0.25s,
|
||||
background-size 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.vp-doc a:not([class]):hover,
|
||||
.vp-doc a:not([class]):focus-visible {
|
||||
background-size:
|
||||
100% 2px,
|
||||
100% 1px;
|
||||
}
|
||||
|
||||
.vp-doc a:not([class]):has(img) {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
/* "Edit this page": a soft brand pill on hover, with the pen tilting as if it starts writing */
|
||||
.VPDocFooter .edit-link-button {
|
||||
margin-left: -10px;
|
||||
padding: 0 12px 0 10px;
|
||||
border-radius: 8px;
|
||||
transition:
|
||||
color 0.25s,
|
||||
background-color 0.25s;
|
||||
}
|
||||
|
||||
.VPDocFooter .edit-link-button:hover,
|
||||
.VPDocFooter .edit-link-button:focus-visible {
|
||||
outline: none;
|
||||
background-color: color-mix(in srgb, var(--vp-c-brand) 10%, transparent);
|
||||
}
|
||||
|
||||
.VPDocFooter .edit-link-icon {
|
||||
transform-origin: 30% 70%;
|
||||
transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
.VPDocFooter .edit-link-button:hover .edit-link-icon,
|
||||
.VPDocFooter .edit-link-button:focus-visible .edit-link-icon {
|
||||
transform: rotate(-14deg) scale(1.1);
|
||||
}
|
||||
|
||||
/* Previous / next page links: Lucide's arrow-left and arrow-right icons point the way, and
|
||||
on hover the card lifts with a light brand tint while its arrow moves on */
|
||||
.VPDocFooter .pager-link {
|
||||
transition:
|
||||
border-color 0.25s,
|
||||
background-color 0.25s,
|
||||
transform 0.25s ease,
|
||||
box-shadow 0.25s;
|
||||
}
|
||||
|
||||
.VPDocFooter .pager-link:hover,
|
||||
.VPDocFooter .pager-link:focus-visible {
|
||||
outline: none;
|
||||
transform: translateY(-2px);
|
||||
background-color: color-mix(in srgb, var(--vp-c-brand) 6%, transparent);
|
||||
box-shadow: var(--vp-shadow-2);
|
||||
}
|
||||
|
||||
.VPDocFooter .pager-link .title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.VPDocFooter .pager-link.next .title {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.VPDocFooter .pager-link.prev .title::before,
|
||||
.VPDocFooter .pager-link.next .title::after {
|
||||
content: '';
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: currentColor;
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
|
||||
.VPDocFooter .pager-link.prev .title::before {
|
||||
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m12 19-7-7 7-7'/%3E%3Cpath d='M19 12H5'/%3E%3C/svg%3E")
|
||||
center / contain no-repeat;
|
||||
}
|
||||
|
||||
.VPDocFooter .pager-link.next .title::after {
|
||||
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3Cpath d='m12 5 7 7-7 7'/%3E%3C/svg%3E")
|
||||
center / contain no-repeat;
|
||||
}
|
||||
|
||||
.VPDocFooter .pager-link.prev:hover .title::before {
|
||||
transform: translateX(-4px);
|
||||
}
|
||||
|
||||
.VPDocFooter .pager-link.next:hover .title::after {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
/* "On this page" outline: each heading is a soft pill on hover and slides in a little,
|
||||
the current heading takes the brand colour and the marker glides to it */
|
||||
.VPDoc .VPDocAsideOutline .outline-link {
|
||||
margin: 0 -8px;
|
||||
padding: 0 8px;
|
||||
border-radius: 6px;
|
||||
transition:
|
||||
color 0.25s,
|
||||
background-color 0.25s,
|
||||
padding-left 0.25s ease;
|
||||
}
|
||||
|
||||
.VPDoc .VPDocAsideOutline .outline-link:hover,
|
||||
.VPDoc .VPDocAsideOutline .outline-link:focus-visible {
|
||||
outline: none;
|
||||
padding-left: 12px;
|
||||
color: var(--vp-c-text-1);
|
||||
background-color: var(--vp-c-default-soft);
|
||||
}
|
||||
|
||||
.VPDoc .VPDocAsideOutline .outline-link.active {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.VPDoc .VPDocAsideOutline .outline-marker {
|
||||
transition:
|
||||
top 0.35s cubic-bezier(0.34, 1.3, 0.64, 1),
|
||||
opacity 0.25s;
|
||||
}
|
||||
|
||||
/* Sidebar scrollbar: a slim rounded thumb that stays quiet until the sidebar is hovered */
|
||||
.VPSidebar::-webkit-scrollbar {
|
||||
width: 0;
|
||||
display: none;
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.VPSidebar::-webkit-scrollbar-track {
|
||||
@@ -188,8 +543,27 @@ html {
|
||||
}
|
||||
|
||||
.VPSidebar::-webkit-scrollbar-thumb {
|
||||
background: transparent;
|
||||
border: none;
|
||||
background-color: var(--vp-c-divider);
|
||||
background-clip: padding-box;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.VPSidebar:hover::-webkit-scrollbar-thumb {
|
||||
background-color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.VPSidebar::-webkit-scrollbar-thumb:hover,
|
||||
.VPSidebar::-webkit-scrollbar-thumb:active {
|
||||
background-color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
/* Browsers without the scrollbar pseudo-elements, such as Firefox */
|
||||
@supports not selector(::-webkit-scrollbar) {
|
||||
.VPSidebar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--vp-c-text-3) transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-block.success {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// Links with a hover underline: the nav bar menu
|
||||
const UNDERLINED_LINKS = '.VPNav .VPNavBarMenuLink, .VPNav .VPNavBarMenuGroup';
|
||||
|
||||
// Makes the hover underline follow the mouse: it draws in from the side the pointer enters
|
||||
// and leaves towards the side the pointer exits. The side is passed to the CSS as a variable.
|
||||
function setUnderlineOrigin(event: PointerEvent) {
|
||||
const link = event.target;
|
||||
if (!(link instanceof HTMLElement) || !link.matches(UNDERLINED_LINKS)) return;
|
||||
|
||||
const { left, width } = link.getBoundingClientRect();
|
||||
const side = event.clientX < left + width / 2 ? 'left' : 'right';
|
||||
|
||||
link.style.setProperty('--underline-origin', side);
|
||||
}
|
||||
|
||||
export default function trackUnderlineDirection() {
|
||||
// pointerenter and pointerleave do not bubble, so they are caught on their way down instead
|
||||
document.addEventListener('pointerenter', setUnderlineOrigin, true);
|
||||
document.addEventListener('pointerleave', setUnderlineOrigin, true);
|
||||
}
|
||||
Reference in New Issue
Block a user