-
+ const submittedBody = (
+ <>
+
Thank you for your feedback!
+ >
+ );
+
+ if (variant === 'aside' && asideEndSlot) {
+ return (
+
+
+ {submittedBody}
+
+
{asideEndSlot}
+
+ );
+ }
+
+ return (
+
+ {submittedBody}
+
+ );
+ }
+
+ if (variant === 'aside') {
+ const buttonsRow = (
+
+
+
+ Yes
+
+
+
+ No
+
+
+ );
+
+ return (
+
+
Was this helpful?
+ {buttonsRow}
+ {feedback === 'no' && (
+
+
+ )}
+ {asideEndSlot ? (
+
{asideEndSlot}
+ ) : null}
);
}
@@ -46,25 +216,41 @@ export default function WasThisHelpful({
Was this helpful?
handleFeedback('yes')}
+ onClick={() => handleCardFeedback('yes')}
aria-label="Yes, this was helpful"
>
-
+
Yes
handleFeedback('no')}
+ onClick={() => handleCardFeedback('no')}
aria-label="No, this was not helpful"
>
-
+
No
@@ -79,8 +265,12 @@ export default function WasThisHelpful({
rows={3}
/>
Submit Feedback
@@ -89,8 +279,3 @@ export default function WasThisHelpful({
);
}
-
-
-
-
-
diff --git a/website/src/components/docs/WasThisHelpful/styles.module.css b/website/src/components/docs/WasThisHelpful/styles.module.css
index 30f78901..e2ec4c99 100644
--- a/website/src/components/docs/WasThisHelpful/styles.module.css
+++ b/website/src/components/docs/WasThisHelpful/styles.module.css
@@ -1,4 +1,263 @@
-/* WasThisHelpful Component */
+/* Static /icons thumbs-up(.svg) + thumbs-up-dark.svg (img) */
+.thumbIcon {
+ flex-shrink: 0;
+ display: block;
+}
+
+/* Selected card button: dark icon → white on primary (light). Dark theme uses light stroke SVG already. */
+.thumbIconOnPrimary {
+ filter: brightness(0) invert(1);
+}
+
+[data-theme='dark'] .thumbIconOnPrimary {
+ filter: none;
+}
+
+/* ----- Aside variant (doc rail, Cloudflare-style) ----- */
+.asideRoot {
+ margin: 0;
+}
+
+.asideLabel {
+ margin: 0 0 0.625rem;
+ font-size: 0.8125rem;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ifm-color-emphasis-600);
+}
+
+@media (min-width: 997px) {
+ .asideLabel {
+ font-size: 0.6875rem;
+ }
+}
+
+/* Grid: one Report link in DOM — beside Yes/No on mobile, below form on desktop */
+.asideRootWithEndSlot {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ column-gap: 0.75rem;
+ row-gap: 0.75rem;
+ align-items: center;
+}
+
+.asideRootWithEndSlot .asideLabel {
+ grid-column: 1 / -1;
+}
+
+.asideRootWithEndSlot .asideButtons {
+ grid-column: 1;
+ grid-row: 2;
+}
+
+.asideRootWithEndSlot .asideEndSlot {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ grid-column: 2;
+ grid-row: 2;
+ width: auto;
+}
+
+.asideRootWithEndSlot .asideFollowup {
+ grid-column: 1 / -1;
+ grid-row: 3;
+ margin-top: 0;
+}
+
+/* Desktop aside: full-width column; Report below follow-up when “No”, else below buttons */
+@media (min-width: 997px) {
+ .asideRootWithEndSlot {
+ grid-template-columns: 1fr;
+ align-items: stretch;
+ }
+
+ .asideRootWithEndSlot .asideButtons {
+ grid-column: 1;
+ grid-row: 2;
+ }
+
+ .asideRootWithEndSlot .asideFollowup {
+ grid-column: 1;
+ grid-row: 3;
+ }
+
+ .asideRootWithEndSlot .asideEndSlot {
+ grid-column: 1;
+ justify-content: flex-start;
+ width: 100%;
+ }
+
+ /* No optional form: buttons row 2, Report row 3 */
+ .asideRootWithEndSlot:not(.asideRootWithFollowup) .asideEndSlot {
+ grid-row: 3;
+ }
+
+ /* With textarea + Submit: Report row 4 */
+ .asideRootWithEndSlot.asideRootWithFollowup .asideEndSlot {
+ grid-row: 4;
+ }
+}
+
+/* Thank-you + Report (no label row) */
+.asideRootSubmittedAside.asideRootWithEndSlot {
+ grid-template-columns: 1fr auto;
+}
+
+.asideRootSubmittedAside.asideRootWithEndSlot .asideSubmittedMain {
+ grid-column: 1;
+ grid-row: 1;
+}
+
+.asideRootSubmittedAside.asideRootWithEndSlot .asideEndSlot {
+ grid-column: 2;
+ grid-row: 1;
+}
+
+@media (min-width: 997px) {
+ .asideRootSubmittedAside.asideRootWithEndSlot {
+ grid-template-columns: 1fr;
+ }
+
+ .asideRootSubmittedAside.asideRootWithEndSlot .asideSubmittedMain {
+ grid-column: 1;
+ grid-row: 1;
+ }
+
+ .asideRootSubmittedAside.asideRootWithEndSlot .asideEndSlot {
+ grid-column: 1;
+ grid-row: 2;
+ width: 100%;
+ }
+}
+
+.asideButtons {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+}
+
+.asideBtn {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.375rem 0.75rem;
+ font-size: 0.8125rem;
+ font-weight: 600;
+ color: var(--ifm-font-color-base);
+ background: var(--ifm-background-surface-color);
+ border: 1px solid var(--ifm-color-emphasis-200);
+ border-radius: 0.375rem;
+ cursor: pointer;
+}
+
+.asideBtn {
+ transition:
+ border-color 160ms cubic-bezier(0.23, 1, 0.32, 1),
+ background-color 160ms cubic-bezier(0.23, 1, 0.32, 1),
+ transform 140ms cubic-bezier(0.23, 1, 0.32, 1);
+}
+
+@media (hover: hover) and (pointer: fine) {
+ .asideBtn:hover {
+ border-color: var(--ifm-color-emphasis-300);
+ background: var(--ifm-color-emphasis-100);
+ }
+}
+
+.asideBtn:active {
+ transform: scale(0.97);
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .asideBtn {
+ transition: border-color 120ms ease, background-color 120ms ease;
+ }
+
+ .asideBtn:active {
+ transform: none;
+ }
+}
+
+[data-theme='dark'] .asideBtn {
+ background: var(--ifm-background-color);
+ border-color: var(--ifm-color-emphasis-300);
+}
+
+.asideFollowup {
+ margin-top: 0.75rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.asideTextarea {
+ width: 100%;
+ padding: 0.5rem 0.625rem;
+ font-family: var(--ifm-font-family-base);
+ font-size: 0.8125rem;
+ color: var(--ifm-font-color-base);
+ background: var(--ifm-background-color);
+ border: 1px solid var(--ifm-color-emphasis-200);
+ border-radius: 0.375rem;
+ resize: vertical;
+ transition: border-color 160ms cubic-bezier(0.23, 1, 0.32, 1);
+}
+
+.asideTextarea:focus {
+ outline: none;
+ border-color: var(--ifm-color-primary);
+}
+
+.asideSubmit {
+ align-self: flex-start;
+ padding: 0.375rem 0.875rem;
+ font-size: 0.8125rem;
+ font-weight: 600;
+ color: #fff;
+ background: var(--ifm-color-primary);
+ border: none;
+ border-radius: 0.375rem;
+ cursor: pointer;
+ transition:
+ background-color 160ms cubic-bezier(0.23, 1, 0.32, 1),
+ transform 140ms cubic-bezier(0.23, 1, 0.32, 1);
+}
+
+@media (hover: hover) and (pointer: fine) {
+ .asideSubmit:hover {
+ background: var(--ifm-color-primary-dark);
+ }
+}
+
+.asideSubmit:active {
+ transform: scale(0.97);
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .asideSubmit {
+ transition: background-color 120ms ease;
+ }
+
+ .asideSubmit:active {
+ transform: none;
+ }
+}
+
+.feedbackSubmittedAside {
+ padding: 0.625rem 0;
+ margin: 0;
+ font-size: 0.8125rem;
+ border: none;
+ background: transparent;
+}
+
+.feedbackSubmittedAside span {
+ font-weight: 500;
+}
+
+/* ----- Card variant ----- */
.wasThisHelpful {
margin: 3rem 0;
padding: 1.5rem;
@@ -35,14 +294,17 @@
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
+ font-size: 0.9375rem;
+ font-weight: 600;
+ color: var(--ifm-font-color-base);
background: var(--ifm-color-emphasis-100);
border: 1px solid var(--ifm-color-emphasis-200);
border-radius: 0.5rem;
cursor: pointer;
- font-size: 0.9375rem;
- font-weight: 600;
- color: var(--ifm-font-color-base);
- transition: all 0.2s ease;
+ transition:
+ border-color 160ms cubic-bezier(0.23, 1, 0.32, 1),
+ background-color 160ms cubic-bezier(0.23, 1, 0.32, 1),
+ transform 140ms cubic-bezier(0.23, 1, 0.32, 1);
}
[data-theme='dark'] .feedbackButton {
@@ -50,9 +312,11 @@
border-color: #2a2a2a;
}
-.feedbackButton:hover {
- background: var(--ifm-color-emphasis-200);
- border-color: var(--ifm-color-primary);
+@media (hover: hover) and (pointer: fine) {
+ .feedbackButton:hover {
+ background: var(--ifm-color-emphasis-200);
+ border-color: var(--ifm-color-primary);
+ }
}
[data-theme='dark'] .feedbackButton:hover {
@@ -60,6 +324,20 @@
border-color: #60a5fa;
}
+.feedbackButton:active {
+ transform: scale(0.97);
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .feedbackButton {
+ transition: border-color 120ms ease, background-color 120ms ease;
+ }
+
+ .feedbackButton:active {
+ transform: none;
+ }
+}
+
.feedbackButtonActive {
background: var(--ifm-color-primary);
border-color: var(--ifm-color-primary);
@@ -89,14 +367,14 @@
width: 100%;
padding: 0.75rem;
margin-bottom: 0.75rem;
- background: var(--ifm-background-color);
- border: 1px solid var(--ifm-color-emphasis-200);
- border-radius: 0.5rem;
font-family: var(--ifm-font-family-base);
font-size: 0.9375rem;
color: var(--ifm-font-color-base);
+ background: var(--ifm-background-color);
+ border: 1px solid var(--ifm-color-emphasis-200);
+ border-radius: 0.5rem;
resize: vertical;
- transition: border-color 0.2s ease;
+ transition: border-color 160ms cubic-bezier(0.23, 1, 0.32, 1);
}
[data-theme='dark'] .commentInput {
@@ -117,25 +395,54 @@
.submitButton {
padding: 0.625rem 1.25rem;
+ font-size: 0.9375rem;
+ font-weight: 600;
+ color: white;
background: var(--ifm-color-primary);
border: none;
border-radius: 0.5rem;
- color: white;
- font-size: 0.9375rem;
- font-weight: 600;
cursor: pointer;
- transition: all 0.2s ease;
+ transition:
+ background-color 160ms cubic-bezier(0.23, 1, 0.32, 1),
+ transform 140ms cubic-bezier(0.23, 1, 0.32, 1);
+}
+
+@media (hover: hover) and (pointer: fine) {
+ .submitButton:hover {
+ background: var(--ifm-color-primary-dark);
+ }
}
-.submitButton:hover {
- background: var(--ifm-color-primary-dark);
- transform: translateY(-1px);
- box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
+.submitButton:active {
+ transform: scale(0.97);
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .submitButton {
+ transition: background-color 120ms ease;
+ }
+
+ .submitButton:active {
+ transform: none;
+ }
}
[data-theme='dark'] .submitButton:hover {
background: #93c5fd;
- box-shadow: 0 4px 8px rgba(96, 165, 250, 0.3);
+}
+
+/* Submit after "No": match light-mode primary in dark theme (#3b82f6 / #2563eb from variables.css) */
+[data-theme='dark'] .asideSubmit.feedbackSubmitNo,
+[data-theme='dark'] .submitButton.feedbackSubmitNo {
+ background: #3b82f6;
+ color: #fff;
+}
+
+@media (hover: hover) and (pointer: fine) {
+ [data-theme='dark'] .asideSubmit.feedbackSubmitNo:hover,
+ [data-theme='dark'] .submitButton.feedbackSubmitNo:hover {
+ background: #2563eb;
+ }
}
.feedbackSubmitted {
@@ -143,24 +450,25 @@
align-items: center;
gap: 0.75rem;
padding: 1rem;
+ font-weight: 600;
+ color: #10b981;
background: rgba(16, 185, 129, 0.1);
border: 1px solid rgba(16, 185, 129, 0.3);
border-radius: 0.5rem;
- color: #10b981;
- font-weight: 600;
}
[data-theme='dark'] .feedbackSubmitted {
+ color: #34d399;
background: rgba(16, 185, 129, 0.15);
border-color: rgba(52, 211, 153, 0.3);
- color: #34d399;
}
-.feedbackSubmitted svg {
+.feedbackSubmittedCheck {
flex-shrink: 0;
+ display: block;
+ color: inherit;
}
-/* Responsive */
@media (max-width: 768px) {
.wasThisHelpful {
padding: 1.25rem;
@@ -180,8 +488,3 @@
justify-content: center;
}
}
-
-
-
-
-
diff --git a/website/src/components/features/FeatureCard/styles.module.css b/website/src/components/features/FeatureCard/styles.module.css
index 2473389b..0bf7f7fa 100644
--- a/website/src/components/features/FeatureCard/styles.module.css
+++ b/website/src/components/features/FeatureCard/styles.module.css
@@ -1,3 +1,5 @@
+/* FeatureCard — same shell as homepage FeaturesSection cards */
+
.cardWrapper {
text-decoration: none;
color: inherit;
@@ -6,14 +8,23 @@
}
.featureCard {
- background: var(--ifm-background-surface-color);
- border-radius: 1rem;
- padding: 2.5rem;
- height: 100%;
- border: 2px solid var(--ifm-color-emphasis-200);
- transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
+ height: 100%;
+ border-radius: 1.25rem;
+ padding: 2.5rem;
overflow: hidden;
+ isolation: isolate;
+ background: linear-gradient(155deg, #ffffff 0%, #f1f5f9 55%, #f8fafc 100%);
+ border: 1px solid rgba(15, 23, 42, 0.08);
+ box-shadow:
+ 0 0 0 1px rgba(255, 255, 255, 0.7) inset,
+ 0 1px 0 rgba(255, 255, 255, 0.9) inset,
+ 0 22px 44px -18px rgba(15, 23, 42, 0.12),
+ 0 4px 12px rgba(15, 23, 42, 0.04);
+ transition:
+ border-color var(--motion-duration-normal) var(--motion-ease-standard),
+ box-shadow var(--motion-duration-normal) var(--motion-ease-standard),
+ transform var(--motion-duration-normal) var(--motion-ease-standard);
}
.featureCard::before {
@@ -22,23 +33,53 @@
top: 0;
left: 0;
right: 0;
- height: 4px;
- background: linear-gradient(90deg, #3b82f6 0%, #1e40af 100%);
+ height: 3px;
+ background: linear-gradient(
+ 90deg,
+ transparent 0%,
+ var(--compose-primary-500) 22%,
+ #60a5fa 55%,
+ rgba(96, 165, 250, 0.35) 100%
+ );
transform: scaleX(0);
- transition: transform 0.4s ease;
+ transform-origin: left;
+ transition: transform 320ms cubic-bezier(0.22, 1, 0.36, 1);
+ z-index: 2;
+ pointer-events: none;
+}
+
+.featureCard::after {
+ content: '';
+ position: absolute;
+ inset: -40% -20% auto -20%;
+ height: 85%;
+ background: radial-gradient(closest-side, rgba(59, 130, 246, 0.14), transparent 72%);
+ opacity: 0;
+ transition: opacity var(--motion-duration-normal) var(--motion-ease-standard);
+ pointer-events: none;
+ z-index: 0;
}
.featureCard:hover {
- transform: translateY(-8px);
- box-shadow: 0 25px 50px -12px rgba(59, 130, 246, 0.25);
- border-color: var(--ifm-color-primary);
+ border-color: rgba(59, 130, 246, 0.28);
+ box-shadow:
+ 0 0 0 1px rgba(59, 130, 246, 0.12) inset,
+ 0 28px 56px -20px rgba(37, 99, 235, 0.18),
+ 0 12px 28px rgba(15, 23, 42, 0.08);
+ transform: translateY(-5px);
}
.featureCard:hover::before {
transform: scaleX(1);
}
+.featureCard:hover::after {
+ opacity: 1;
+}
+
.cardIcon {
+ position: relative;
+ z-index: 1;
font-size: 3.5rem;
margin-bottom: 1.5rem;
background: linear-gradient(135deg, #3b82f6 0%, #0ea5e9 100%);
@@ -51,44 +92,124 @@
}
.cardTitle {
- font-size: 1.5rem;
- font-weight: 700;
+ position: relative;
+ z-index: 1;
+ font-size: 1.3125rem;
+ font-weight: 750;
+ letter-spacing: -0.025em;
+ line-height: 1.22;
margin-bottom: 1rem;
- color: var(--ifm-color-emphasis-900);
+ color: #0f172a;
}
.cardDescription {
- font-size: 1rem;
- line-height: 1.7;
- color: var(--ifm-color-emphasis-700);
+ position: relative;
+ z-index: 1;
+ font-size: 0.96875rem;
+ line-height: 1.65;
+ color: #334155;
margin-bottom: 0;
}
.cardLink {
+ position: relative;
+ z-index: 1;
margin-top: 1.5rem;
- color: var(--ifm-color-primary);
+ color: #1e40af;
font-weight: 600;
+ font-size: 0.6875rem;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
display: flex;
align-items: center;
- gap: 0.5rem;
- transition: gap 0.3s ease;
+ gap: 0.4rem;
+ transition:
+ color var(--motion-duration-normal) var(--motion-ease-standard),
+ gap var(--motion-duration-normal) var(--motion-ease-standard);
}
.featureCard:hover .cardLink {
- gap: 1rem;
+ color: #1d4ed8;
+ gap: 0.65rem;
}
-/* Gradient variations */
+/* Optional gradient accents on top facet only */
.featureCard.gradientBlue::before {
- background: linear-gradient(90deg, #60a5fa 0%, #2563eb 100%);
+ background: linear-gradient(
+ 90deg,
+ transparent 0%,
+ #60a5fa 22%,
+ #2563eb 55%,
+ rgba(37, 99, 235, 0.35) 100%
+ );
}
.featureCard.gradientCyan::before {
- background: linear-gradient(90deg, #06b6d4 0%, #3b82f6 100%);
+ background: linear-gradient(
+ 90deg,
+ transparent 0%,
+ #22d3ee 22%,
+ #3b82f6 55%,
+ rgba(59, 130, 246, 0.35) 100%
+ );
}
.featureCard.gradientGreen::before {
- background: linear-gradient(90deg, #10b981 0%, #34d399 100%);
+ background: linear-gradient(
+ 90deg,
+ transparent 0%,
+ #34d399 22%,
+ #10b981 55%,
+ rgba(16, 185, 129, 0.35) 100%
+ );
+}
+
+[data-theme='dark'] .featureCard {
+ background: linear-gradient(
+ 155deg,
+ rgba(30, 41, 59, 0.92) 0%,
+ rgba(15, 23, 42, 0.72) 48%,
+ rgba(10, 14, 26, 0.55) 100%
+ );
+ backdrop-filter: blur(14px);
+ -webkit-backdrop-filter: blur(14px);
+ border-color: rgba(148, 163, 184, 0.14);
+ box-shadow:
+ 0 0 0 1px rgba(0, 0, 0, 0.45) inset,
+ 0 1px 0 rgba(255, 255, 255, 0.06) inset,
+ 0 28px 56px -24px rgba(0, 0, 0, 0.65),
+ 0 0 0 1px rgba(59, 130, 246, 0.06);
+}
+
+[data-theme='dark'] .featureCard::after {
+ background: radial-gradient(closest-side, rgba(96, 165, 250, 0.22), transparent 70%);
+}
+
+[data-theme='dark'] .featureCard:hover {
+ border-color: rgba(96, 165, 250, 0.35);
+ box-shadow:
+ 0 0 0 1px rgba(59, 130, 246, 0.2) inset,
+ 0 1px 0 rgba(255, 255, 255, 0.09) inset,
+ 0 0 40px -8px rgba(59, 130, 246, 0.35),
+ 0 32px 64px -28px rgba(0, 0, 0, 0.75);
+}
+
+[data-theme='dark'] .cardTitle {
+ color: #ffffff;
+}
+
+[data-theme='dark'] .cardDescription {
+ color: rgba(226, 232, 240, 0.82);
+}
+
+[data-theme='dark'] .cardLink {
+ color: #93c5fd;
+ opacity: 0.5;
+}
+
+[data-theme='dark'] .featureCard:hover .cardLink {
+ color: #4189f7;
+ opacity: 0.95;
}
@media (max-width: 768px) {
@@ -102,7 +223,26 @@
}
.cardTitle {
- font-size: 1.25rem;
+ font-size: 1.1875rem;
}
}
+@media (prefers-reduced-motion: reduce) {
+ .featureCard {
+ transition:
+ border-color var(--motion-duration-fast) ease,
+ box-shadow var(--motion-duration-fast) ease;
+ }
+
+ .featureCard:hover {
+ transform: none;
+ }
+
+ .featureCard::before {
+ transition: transform 0.01ms;
+ }
+
+ .featureCard:hover .cardLink {
+ gap: 0.4rem;
+ }
+}
diff --git a/website/src/components/features/FeatureGrid/styles.module.css b/website/src/components/features/FeatureGrid/styles.module.css
index dfd971a5..35382675 100644
--- a/website/src/components/features/FeatureGrid/styles.module.css
+++ b/website/src/components/features/FeatureGrid/styles.module.css
@@ -1,4 +1,5 @@
-/* FeatureGrid Component */
+/* FeatureGrid — items match homepage / DocCard feature shell */
+
.featureGrid {
display: grid;
grid-template-columns: repeat(var(--grid-columns, 2), 1fr);
@@ -6,7 +7,6 @@
margin: 2.5rem 0;
}
-/* When sidebar is visible on narrower screens, switch to single column for better fit */
@media (min-width: 997px) and (max-width: 1200px) {
.featureGrid {
grid-template-columns: 1fr;
@@ -14,7 +14,6 @@
}
}
-/* Wider screens with sidebar, keep 2 columns */
@media (min-width: 1201px) {
.featureGrid {
grid-template-columns: repeat(var(--grid-columns, 2), 1fr);
@@ -22,7 +21,6 @@
}
}
-/* Tablet range - 2 columns */
@media (min-width: 689px) and (max-width: 996px) {
.featureGrid {
grid-template-columns: repeat(2, 1fr);
@@ -30,7 +28,6 @@
}
}
-/* Mobile - single column with adjusted spacing */
@media (max-width: 688px) {
.featureGrid {
grid-template-columns: 1fr;
@@ -38,24 +35,27 @@
}
}
-/* FeatureItem */
.featureItem {
+ position: relative;
+ display: flex;
+ flex-direction: column;
padding: 2rem;
- background: var(--ifm-background-surface-color);
- border: 1px solid var(--ifm-color-emphasis-200);
- border-radius: 0.75rem;
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
text-decoration: none;
color: inherit;
- display: flex;
- flex-direction: column;
- position: relative;
overflow: hidden;
-}
-
-[data-theme='dark'] .featureItem {
- background: #0f0f0f;
- border-color: #2a2a2a;
+ isolation: isolate;
+ border-radius: 1.25rem;
+ background: linear-gradient(155deg, #ffffff 0%, #f1f5f9 55%, #f8fafc 100%);
+ border: 1px solid rgba(15, 23, 42, 0.08);
+ box-shadow:
+ 0 0 0 1px rgba(255, 255, 255, 0.7) inset,
+ 0 1px 0 rgba(255, 255, 255, 0.9) inset,
+ 0 22px 44px -18px rgba(15, 23, 42, 0.12),
+ 0 4px 12px rgba(15, 23, 42, 0.04);
+ transition:
+ border-color var(--motion-duration-normal) var(--motion-ease-standard),
+ box-shadow var(--motion-duration-normal) var(--motion-ease-standard),
+ transform var(--motion-duration-normal) var(--motion-ease-standard);
}
.featureItem::before {
@@ -65,99 +65,174 @@
left: 0;
right: 0;
height: 3px;
- background: linear-gradient(90deg, var(--ifm-color-primary) 0%, var(--ifm-color-accent) 100%);
+ background: linear-gradient(
+ 90deg,
+ transparent 0%,
+ var(--compose-primary-500) 22%,
+ #60a5fa 55%,
+ rgba(96, 165, 250, 0.35) 100%
+ );
+ transform: scaleX(0);
+ transform-origin: left;
+ transition: transform 320ms cubic-bezier(0.22, 1, 0.36, 1);
+ z-index: 2;
+ pointer-events: none;
+}
+
+.featureItem::after {
+ content: '';
+ position: absolute;
+ inset: -40% -20% auto -20%;
+ height: 85%;
+ background: radial-gradient(closest-side, rgba(59, 130, 246, 0.14), transparent 72%);
opacity: 0;
- transition: opacity 0.3s ease;
+ transition: opacity var(--motion-duration-normal) var(--motion-ease-standard);
+ pointer-events: none;
+ z-index: 0;
}
.featureItem:hover {
- transform: translateY(-4px);
- border-color: var(--ifm-color-primary);
- box-shadow: 0 12px 24px -6px rgba(59, 130, 246, 0.2);
+ border-color: rgba(59, 130, 246, 0.28);
+ box-shadow:
+ 0 0 0 1px rgba(59, 130, 246, 0.12) inset,
+ 0 28px 56px -20px rgba(37, 99, 235, 0.18),
+ 0 12px 28px rgba(15, 23, 42, 0.08);
+ transform: translateY(-5px);
}
-[data-theme='dark'] .featureItem:hover {
- border-color: #60a5fa;
- box-shadow: 0 12px 24px -6px rgba(96, 165, 250, 0.2);
+.featureItem:hover::before {
+ transform: scaleX(1);
}
-.featureItem:hover::before {
+.featureItem:hover::after {
opacity: 1;
}
-/* Feature Icon */
+[data-theme='dark'] .featureItem {
+ background: linear-gradient(
+ 155deg,
+ rgba(30, 41, 59, 0.92) 0%,
+ rgba(15, 23, 42, 0.72) 48%,
+ rgba(10, 14, 26, 0.55) 100%
+ );
+ backdrop-filter: blur(14px);
+ -webkit-backdrop-filter: blur(14px);
+ border-color: rgba(148, 163, 184, 0.14);
+ box-shadow:
+ 0 0 0 1px rgba(0, 0, 0, 0.45) inset,
+ 0 1px 0 rgba(255, 255, 255, 0.06) inset,
+ 0 28px 56px -24px rgba(0, 0, 0, 0.65),
+ 0 0 0 1px rgba(59, 130, 246, 0.06);
+}
+
+[data-theme='dark'] .featureItem::after {
+ background: radial-gradient(closest-side, rgba(96, 165, 250, 0.22), transparent 70%);
+}
+
+[data-theme='dark'] .featureItem:hover {
+ border-color: rgba(96, 165, 250, 0.35);
+ box-shadow:
+ 0 0 0 1px rgba(59, 130, 246, 0.2) inset,
+ 0 1px 0 rgba(255, 255, 255, 0.09) inset,
+ 0 0 40px -8px rgba(59, 130, 246, 0.35),
+ 0 32px 64px -28px rgba(0, 0, 0, 0.75);
+}
+
.featureIcon {
+ position: relative;
+ z-index: 1;
display: flex;
align-items: center;
justify-content: center;
width: 4rem;
height: 4rem;
- border-radius: 0.75rem;
- background: linear-gradient(135deg, var(--ifm-color-primary-lightest) 0%, var(--ifm-color-primary-lighter) 100%);
margin-bottom: 1.5rem;
- transition: transform 0.3s ease;
+ border-radius: 0.75rem;
+ color: var(--compose-primary-500);
+ background: linear-gradient(145deg, rgba(59, 130, 246, 0.14), rgba(37, 99, 235, 0.06));
+ border: 1px solid rgba(59, 130, 246, 0.22);
+ box-shadow: 0 1px 0 rgba(255, 255, 255, 0.75) inset;
+ transition:
+ border-color var(--motion-duration-normal) var(--motion-ease-standard),
+ box-shadow var(--motion-duration-normal) var(--motion-ease-standard);
}
[data-theme='dark'] .featureIcon {
- background: linear-gradient(135deg, rgba(96, 165, 250, 0.15) 0%, rgba(59, 130, 246, 0.15) 100%);
-}
-
-.featureItem:hover .featureIcon {
- transform: scale(1.1) rotate(5deg);
-}
-
-.iconEmoji {
- font-size: 2rem;
- line-height: 1;
+ background: linear-gradient(145deg, rgba(59, 130, 246, 0.2), rgba(15, 23, 42, 0.35));
+ border-color: rgba(96, 165, 250, 0.26);
+ box-shadow: 0 1px 0 rgba(255, 255, 255, 0.06) inset;
}
-/* Feature Title */
.featureTitle {
+ position: relative;
+ z-index: 1;
margin: 0 0 0.875rem 0;
font-size: 1.25rem;
- font-weight: 700;
- color: var(--ifm-heading-color);
- letter-spacing: -0.01em;
-}
-
-[data-theme='dark'] .featureTitle {
- color: #ffffff;
+ font-weight: 750;
+ letter-spacing: -0.025em;
+ line-height: 1.22;
+ color: #0f172a;
}
-/* Feature Description */
.featureDescription {
+ position: relative;
+ z-index: 1;
margin: 0;
- font-size: 0.95rem;
- line-height: 1.6;
- color: var(--ifm-color-emphasis-700);
+ font-size: 0.96875rem;
+ line-height: 1.65;
+ color: #334155;
flex: 1;
}
+[data-theme='dark'] .featureTitle {
+ color: #ffffff;
+}
+
[data-theme='dark'] .featureDescription {
- color: #a0a0a0;
+ color: rgba(226, 232, 240, 0.82);
+}
+
+.iconEmoji {
+ font-size: 2rem;
+ line-height: 1;
}
-/* Responsive adjustments */
@media (max-width: 768px) {
.featureItem {
- padding: 1.5rem;
+ padding: 1.75rem 1.5rem 2rem;
}
-
+
.featureIcon {
width: 3.5rem;
height: 3.5rem;
margin-bottom: 1.25rem;
}
-
+
.iconEmoji {
font-size: 1.75rem;
}
-
+
.featureTitle {
font-size: 1.125rem;
}
-
+
.featureDescription {
- font-size: 0.9rem;
+ font-size: 0.9375rem;
}
-}
\ No newline at end of file
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .featureItem {
+ transition:
+ border-color var(--motion-duration-fast) ease,
+ box-shadow var(--motion-duration-fast) ease;
+ }
+
+ .featureItem:hover {
+ transform: none;
+ }
+
+ .featureItem::before {
+ transition: transform 0.01ms;
+ }
+}
diff --git a/website/src/components/footer/NetlifyFooterBadge.js b/website/src/components/footer/NetlifyFooterBadge.js
new file mode 100644
index 00000000..c90c3859
--- /dev/null
+++ b/website/src/components/footer/NetlifyFooterBadge.js
@@ -0,0 +1,21 @@
+import React from 'react';
+
+/**
+ * Netlify Open Source plan attribution — keep visible and linked to netlify.com.
+ */
+export default function NetlifyFooterBadge() {
+ return (
+
- Start building your next smart contract system.
+ Install Compose and put together a diamond-based system you can grow one facet at a time.
diff --git a/website/src/components/home/FeaturesSection.js b/website/src/components/home/FeaturesSection.js
index 33a873fb..c6644e5e 100644
--- a/website/src/components/home/FeaturesSection.js
+++ b/website/src/components/home/FeaturesSection.js
@@ -12,7 +12,7 @@ export default function FeaturesSection() {
{
kicker: 'ERC-2535',
title: 'Diamond-Native',
- description: 'Built specifically for ERC-2535 Diamonds. Deploy facets once, reuse them across multiple diamonds onchain.',
+ description: 'Deploy facets once, reuse them across multiple diamonds on chain.',
link: '/docs/foundations/diamond-contracts',
},
{
@@ -51,11 +51,11 @@ export default function FeaturesSection() {
Forget traditional smart contract design patterns. Compose takes a radically
- different approach with Smart Contract Oriented Programming.
+ different approach with Smart Contract Oriented Programming (SCOP) .
- We focus on building small, independent, and easy-to-understand smart contracts called facets .
+ We focus on building small, independent, and easy-to-understand smart contracts called facets .
Each facet is designed to be deployed once, then reused and composed seamlessly with others to form
complete smart contract systems.
diff --git a/website/src/components/home/HomepageHeader.js b/website/src/components/home/HomepageHeader.js
index 3df1453a..6bd37c95 100644
--- a/website/src/components/home/HomepageHeader.js
+++ b/website/src/components/home/HomepageHeader.js
@@ -15,11 +15,6 @@ export default function HomepageHeader() {
const badgeAndTitle = (
<>
-
-
- Not Production Ready
-
-
Build the future of
Smart Contracts
@@ -37,26 +32,27 @@ export default function HomepageHeader() {
Get Started
-
+
+
+
Learn Core Concepts
-
>
);
diff --git a/website/src/components/home/codeShowcase.module.css b/website/src/components/home/codeShowcase.module.css
index 8cab9255..e26135f5 100644
--- a/website/src/components/home/codeShowcase.module.css
+++ b/website/src/components/home/codeShowcase.module.css
@@ -139,39 +139,180 @@
width: 100%;
max-width: 100%;
box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25), 0 0 0 1px rgba(255,255,255,0.05);
- transition: all var(--motion-duration-normal) var(--motion-ease-standard);
+ display: flex;
+ flex-direction: column;
+}
+
+[data-theme='light'] .codeWindow {
+ /* One surface with docs code bg — avoids ring + seam next to Prism block */
+ background: #f8fafc;
+ box-shadow: 0 25px 50px -12px rgba(15, 23, 42, 0.07);
+}
+
+/* Homepage code column only (not narrow slide-over panel): edge definition on large viewports */
+@media screen and (min-width: 1101px) {
+ [data-theme='light'] .showcaseCode .codeWindow {
+ border: 1px solid rgba(15, 23, 42, 0.12);
+ }
+
+ [data-theme='dark'] .showcaseCode .codeWindow {
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ }
+}
+
+[data-theme='light'] .codeWindowHeader {
+ background: #f8fafc;
+ border-bottom: none;
+ /* Subtle separator — no hard 1px line against the code area */
+ box-shadow: inset 0 -1px 0 rgba(148, 163, 184, 0.2);
}
-.codeWindow:hover { transform: translateY(-4px); box-shadow: 0 35px 60px -15px rgba(0,0,0,0.3), 0 0 0 1px rgba(255,255,255,0.05); }
+
+[data-theme='light'] .codeWindowTitle {
+ color: #475569;
+}
+
+/* Pull embedded CodeBlock flush with header (same bg as global .theme-code-block light) */
+[data-theme='light'] .codeShowcaseBlockRoot :global(.theme-code-block) {
+ background: #f8fafc !important;
+}
+
+[data-theme='light'] .codeShowcaseBlockRoot :global(div[class*='codeBlockContainer']) {
+ background: #f8fafc !important;
+ background-color: #f8fafc !important;
+}
+
+/* Dark: match light-mode treatment — one slate surface, no hard ring/seam */
+[data-theme='dark'] .codeWindow {
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.45);
+}
+
+[data-theme='dark'] .codeWindowHeader {
+ border-bottom: none;
+ box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.06);
+}
+
+/* Docs global code-blocks.css uses #1a1a1a — override so panel matches .codeWindow (#1e293b) */
+[data-theme='dark'] .codeShowcaseBlockRoot :global(.theme-code-block),
+[data-theme='dark'] .codeShowcaseBlockRoot :global(.theme-code-block > div),
+[data-theme='dark'] .codeShowcaseBlockRoot :global(.theme-code-block > div > div) {
+ background: #1e293b !important;
+ background-color: #1e293b !important;
+}
+
+[data-theme='dark'] .codeShowcaseBlockRoot :global(div[class*='codeBlockContainer']),
+[data-theme='dark'] .codeShowcaseBlockRoot :global(div[class*='codeBlockContent']),
+[data-theme='dark'] .codeShowcaseBlockRoot :global(div[class*='codeBlockLines']) {
+ background: #1e293b !important;
+ background-color: #1e293b !important;
+}
+
+[data-theme='dark'] .codeShowcaseBlockRoot :global(pre.codeBlock) {
+ background: #1e293b !important;
+ background-color: #1e293b !important;
+}
+
.codeWindowHeader { display: flex; align-items: center; padding: 1rem 1.5rem; background: #0f172a; border-bottom: 1px solid rgba(255,255,255,0.05); }
+.codeWindowHeaderPanel { justify-content: space-between; gap: 1rem; }
.codeWindowDots { display: flex; gap: 0.5rem; margin-right: auto; }
.codeWindowDots span { width: 12px; height: 12px; border-radius: 50%; background: #334155; }
.codeWindowDots span:nth-child(1) { background: #ef4444; }
.codeWindowDots span:nth-child(2) { background: #f59e0b; }
.codeWindowDots span:nth-child(3) { background: #10b981; }
.codeWindowTitle { font-family: var(--ifm-font-family-monospace); font-size: 0.875rem; color: #94a3b8; font-weight: 500; }
-.codeWindowContent {
- padding: 2rem;
- margin: 0;
+
+/* @theme/CodeBlock embed (Solidity) — see ShowcaseSolidityCode in CodeShowcase.js */
+.codeShowcaseBlockRoot {
min-width: 0;
- max-width: 100%;
- box-sizing: border-box;
- font-family: var(--ifm-font-family-monospace);
+ width: 100%;
+}
+
+.codeShowcaseBlockRoot :global(.theme-code-block) {
+ margin-bottom: 0 !important;
+ box-shadow: none !important;
+}
+
+/* Toolbar (copy / wrap): hide the whole Docusaurus button row until the faux window is hovered.
+ Opacity-only on can still leave visible borders/backgrounds in some cases; collapsing
+ the wrapper avoids that. Keyboard: :focus-within keeps the row available while tabbing. */
+.codeWindow:has(.codeShowcaseBlockRoot):not(:hover)
+ .codeShowcaseBlockRoot
+ :global(div[class*='buttonGroup']):not(:focus-within) {
+ opacity: 0 !important;
+ visibility: hidden !important;
+ pointer-events: none !important;
+}
+
+.codeWindow:has(.codeShowcaseBlockRoot):hover
+ .codeShowcaseBlockRoot
+ :global(div[class*='buttonGroup']),
+.codeWindow:has(.codeShowcaseBlockRoot)
+ .codeShowcaseBlockRoot
+ :global(div[class*='buttonGroup']):focus-within {
+ opacity: 1 !important;
+ visibility: visible !important;
+ pointer-events: auto !important;
+}
+
+.codeWindow:has(.codeShowcaseBlockRoot):hover
+ .codeShowcaseBlockRoot
+ :global(div[class*='buttonGroup'])
+ button {
+ opacity: 0.4 !important;
+ pointer-events: auto;
+}
+
+.codeWindow:has(.codeShowcaseBlockRoot)
+ .codeShowcaseBlockRoot
+ :global(div[class*='buttonGroup'])
+ button:hover,
+.codeWindow:has(.codeShowcaseBlockRoot)
+ .codeShowcaseBlockRoot
+ :global(div[class*='buttonGroup'])
+ button:focus-visible {
+ opacity: 1 !important;
+ pointer-events: auto;
+}
+
+.showcaseCode .codeWindow .codeShowcaseBlockRoot :global(.theme-code-block) {
+ border-radius: 0 0 1.25rem 1.25rem;
+}
+
+.codeShowcaseBlockRoot :global(pre.codeBlock) {
+ margin: 0;
font-size: 0.9375rem;
line-height: 1.7;
- color: #e2e8f0;
- overflow-x: auto;
- -webkit-overflow-scrolling: touch;
- overscroll-behavior-x: contain;
- background: #1e293b;
- white-space: pre;
tab-size: 2;
scrollbar-width: thin;
+}
+
+.codeShowcaseBlockRoot :global(pre.codeBlock)::-webkit-scrollbar {
+ height: 8px;
+ width: 8px;
+}
+
+.codeShowcaseBlockRoot :global(pre.codeBlock)::-webkit-scrollbar-thumb {
+ border-radius: 4px;
+}
+
+[data-theme='dark'] .codeShowcaseBlockRoot :global(pre.codeBlock) {
scrollbar-color: #475569 #1e293b;
}
-.codeWindowContent::-webkit-scrollbar { height: 8px; }
-.codeWindowContent::-webkit-scrollbar-track { background: #1e293b; }
-.codeWindowContent::-webkit-scrollbar-thumb { background: #475569; border-radius: 4px; }
-.codeWindowContent::-webkit-scrollbar-thumb:hover { background: #64748b; }
+
+[data-theme='dark'] .codeShowcaseBlockRoot :global(pre.codeBlock)::-webkit-scrollbar-track {
+ background: #1e293b;
+}
+
+[data-theme='dark'] .codeShowcaseBlockRoot :global(pre.codeBlock)::-webkit-scrollbar-thumb {
+ background: #475569;
+}
+
+[data-theme='dark'] .codeShowcaseBlockRoot :global(pre.codeBlock)::-webkit-scrollbar-thumb:hover {
+ background: #64748b;
+}
+
+[data-theme='light'] .codeShowcaseBlockRoot :global(pre.codeBlock)::-webkit-scrollbar-thumb {
+ background: #cbd5e1;
+}
/* Responsive — stack before the two-column layout gets cramped */
@media screen and (max-width: 1100px) {
@@ -201,7 +342,10 @@
.showcaseFeature p { font-size: 0.875rem; }
.showcaseCode { padding: 0 1rem; width: 100%; max-width: 100%; margin: 0 auto; min-width: 0; align-self: stretch; }
.codeWindow { max-width: 100%; width: 100%; margin: 0 auto; min-width: 0; }
- .codeWindowContent { padding: 1.5rem; font-size: 0.8125rem; line-height: 1.6; }
+ .codeShowcaseBlockRoot :global(pre.codeBlock) {
+ font-size: 0.8125rem;
+ line-height: 1.6;
+ }
}
@media screen and (max-width: 480px) {
.showcaseSection { padding: 3rem 0; }
@@ -216,10 +360,16 @@
.showcaseLink { margin-left: auto; margin-right: auto; }
.showcaseCode { padding: 0 1rem; width: 100%; max-width: 100%; margin: 0 auto; min-width: 0; align-self: stretch; }
.codeWindow { border-radius: 1rem; width: 100%; max-width: min(100%, calc(100vw - 2rem)); margin: 0 auto; min-width: 0; }
+ .showcaseCode .codeWindow .codeShowcaseBlockRoot :global(.theme-code-block) {
+ border-radius: 0 0 1rem 1rem;
+ }
.codeWindowHeader { padding: 0.75rem 1rem; }
.codeWindowTitle { font-size: 0.75rem; }
.codeWindowDots span { width: 10px; height: 10px; }
- .codeWindowContent { font-size: 0.75rem; padding: 1rem; line-height: 1.5; }
+ .codeShowcaseBlockRoot :global(pre.codeBlock) {
+ font-size: 0.75rem;
+ line-height: 1.5;
+ }
}
/* Dark mode */
@@ -270,3 +420,222 @@
}
[data-theme='dark'] .showcaseFeature h4 { color: #ffffff; }
[data-theme='dark'] .showcaseFeature p { color: rgba(255,255,255,0.7); }
+
+/* Narrow viewport: slide-over code panel (see home/CodeShowcase.js) */
+.codeExampleTrigger {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ margin-top: 1rem;
+ margin-bottom: 0;
+ padding: 1rem 1.75rem;
+ width: 100%;
+ max-width: 600px;
+ box-sizing: border-box;
+ border: none;
+ border-radius: 0.75rem;
+ cursor: pointer;
+ font-size: 1rem;
+ font-weight: 600;
+ font-family: inherit;
+ color: #fff;
+ background: linear-gradient(135deg, var(--compose-primary-500), var(--compose-primary-600));
+ box-shadow: 0 4px 14px rgba(59, 130, 246, 0.4);
+ transition:
+ transform 160ms cubic-bezier(0.23, 1, 0.32, 1),
+ box-shadow 160ms cubic-bezier(0.23, 1, 0.32, 1);
+}
+
+.codeExampleTrigger:active {
+ transform: scale(0.97);
+}
+
+.codePanelBackdrop {
+ position: fixed;
+ inset: 0;
+ z-index: 340;
+ background: rgba(15, 23, 42, 0.48);
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 280ms cubic-bezier(0.23, 1, 0.32, 1);
+}
+
+.codePanelBackdropVisible {
+ opacity: 1;
+ pointer-events: auto;
+}
+
+.codePanel {
+ position: fixed;
+ top: 50%;
+ right: 0;
+ z-index: 350;
+ display: flex;
+ flex-direction: column;
+ width: min(90vw, 520px);
+ max-width: calc(100vw - 24px);
+ /* Fit content height; cap at full viewport */
+ height: fit-content;
+ max-height: 100vh;
+ max-height: 100dvh;
+ padding-top: env(safe-area-inset-top, 0px);
+ padding-bottom: env(safe-area-inset-bottom, 0px);
+ box-sizing: border-box;
+ overflow: hidden;
+ /* Vertically centered; hidden state slides off to the right */
+ transform: translateY(-50%) translateX(100%);
+ pointer-events: none;
+ transition: transform 280ms cubic-bezier(0.23, 1, 0.32, 1);
+ filter: drop-shadow(-12px 0 24px rgba(0, 0, 0, 0.18));
+}
+
+.codePanelVisible {
+ transform: translateY(-50%) translateX(0);
+ pointer-events: auto;
+}
+
+.codePanelInner {
+ flex: 1 1 auto;
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+ padding: 0.75rem 0 0.75rem 0.75rem;
+}
+
+.codePanelInner .codeWindow {
+ /* Grow only when panel hits max-height so pre can scroll; otherwise hug content */
+ flex: 0 1 auto;
+ min-height: 0;
+ max-height: 100%;
+ display: flex;
+ flex-direction: column;
+ border-radius: 1rem 0 0 1rem;
+ overflow: hidden;
+}
+
+.codePanelInner .codeWindow .codeShowcaseBlockRoot :global(.theme-code-block) {
+ border-radius: 0 0 0 1rem;
+}
+
+.codePanelInner .codeShowcaseBlockRoot {
+ flex: 0 1 auto;
+ min-height: 0;
+ max-height: 100%;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.codePanelInner .codeShowcaseBlockRoot :global(.theme-code-block) {
+ flex: 1 1 auto;
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.codePanelInner .codeShowcaseBlockRoot :global(div[class*='codeBlockContent']) {
+ flex: 1 1 auto;
+ min-height: 0;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+}
+
+.codePanelInner .codeShowcaseBlockRoot :global(pre.codeBlock) {
+ flex: 1 1 auto;
+ min-height: 0;
+ overflow: auto;
+ -webkit-overflow-scrolling: touch;
+}
+
+/* Match @theme/CodeBlock toolbar buttons (Buttons/styles.module.css) */
+.codePanelClose {
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ width: auto;
+ height: auto;
+ min-width: 2.25rem;
+ min-height: 2.25rem;
+ margin: 0;
+ padding: 0.4rem;
+ border: 1px solid var(--ifm-color-emphasis-300);
+ border-radius: var(--ifm-global-radius);
+ cursor: pointer;
+ line-height: 0;
+ transition:
+ color 160ms cubic-bezier(0.23, 1, 0.32, 1),
+ background 160ms cubic-bezier(0.23, 1, 0.32, 1),
+ border-color 160ms cubic-bezier(0.23, 1, 0.32, 1),
+ transform 160ms cubic-bezier(0.23, 1, 0.32, 1);
+}
+
+[data-theme='light'] .codePanelClose {
+ background: #ffffff;
+ color: #64748b;
+}
+
+[data-theme='light'] .codePanelClose:hover {
+ color: #334155;
+ background: #f1f5f9;
+ border-color: #cbd5e1;
+}
+
+/* Dark: same surface + border language as code block copy/wrap controls */
+[data-theme='dark'] .codePanelClose {
+ background: #1e293b;
+ color: #cbd5e1;
+}
+
+[data-theme='dark'] .codePanelClose:hover {
+ color: #f1f5f9;
+ background: #334155;
+ border-color: #64748b;
+}
+
+.codePanelClose:active {
+ transform: scale(0.97);
+}
+
+.codePanelCloseIcon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+[data-theme='dark'] .codePanelBackdrop {
+ background: rgba(2, 6, 23, 0.65);
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .codeExampleTrigger {
+ transition: none;
+ }
+
+ .codeExampleTrigger:active {
+ transform: none;
+ }
+
+ .codePanelBackdrop,
+ .codePanel {
+ transition-duration: 0.01ms;
+ }
+
+ .codePanelClose {
+ transition-duration: 0.01ms;
+ }
+
+ .codePanelClose:active {
+ transform: none;
+ }
+}
+
+@media screen and (max-width: 1100px) {
+ .showcaseFeatures {
+ margin-bottom: 0;
+ }
+}
diff --git a/website/src/components/home/ctaSection.module.css b/website/src/components/home/ctaSection.module.css
index daf18497..d20147be 100644
--- a/website/src/components/home/ctaSection.module.css
+++ b/website/src/components/home/ctaSection.module.css
@@ -125,7 +125,7 @@
margin: 0 auto 1.75rem;
font-size: clamp(0.95rem, 2vw, 1.0625rem);
line-height: 1.65;
- color: rgba(255, 255, 255, 0.88);
+ color: rgba(255, 255, 255);
max-width: 32rem;
}
diff --git a/website/src/components/home/homepageHeader.module.css b/website/src/components/home/homepageHeader.module.css
index f96db939..4e92bb48 100644
--- a/website/src/components/home/homepageHeader.module.css
+++ b/website/src/components/home/homepageHeader.module.css
@@ -2,7 +2,7 @@
.heroBanner {
position: relative;
- padding: 6rem 0 14rem;
+ padding: 10rem 0 14rem;
overflow: hidden;
min-height: auto;
display: flex;
@@ -171,42 +171,77 @@
display: flex;
align-items: center;
justify-content: flex-start;
- gap: 1.25rem;
+ gap: 0.75rem 1rem;
flex-wrap: wrap;
margin-bottom: 1.5rem;
animation: fadeInUp var(--motion-duration-normal) var(--motion-ease-standard) 0.5s both;
}
+/* Match footer CtaSection: pill CTAs, white primary + outline secondary on dark hero */
.ctaButton {
display: inline-flex;
align-items: center;
justify-content: center;
- gap: 0.625rem;
- padding: 1rem 2rem;
- font-size: 1rem;
+ gap: 0.5rem;
+ padding: 0.85rem 1.5rem;
+ font-size: 0.9375rem;
font-weight: 600;
- border-radius: 0.75rem;
+ border-radius: 9999px;
text-decoration: none;
- transition: all var(--motion-duration-normal) var(--motion-ease-standard);
border: 2px solid transparent;
white-space: nowrap;
- box-shadow: 0 4px 6px rgba(0,0,0,0.2);
- max-width: 220px;
+ transition:
+ transform var(--motion-duration-normal) var(--motion-ease-standard),
+ box-shadow var(--motion-duration-normal) var(--motion-ease-standard),
+ background var(--motion-duration-normal) var(--motion-ease-standard),
+ border-color var(--motion-duration-normal) var(--motion-ease-standard),
+ color var(--motion-duration-normal) var(--motion-ease-standard);
}
.ctaPrimary {
- background: linear-gradient(135deg, var(--compose-primary-500) 0%, var(--compose-primary-600) 100%);
- color: white;
- box-shadow:
- 0 4px 14px rgba(59, 130, 246, 0.4),
- 0 1px 3px rgba(0, 0, 0, 0.12);
+ color: #0f172a;
+ background: #ffffff;
+ border-color: rgba(255, 255, 255, 0.95);
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+}
+
+.ctaPrimary:hover {
+ color: #020617;
+ background: #f8fafc;
+ transform: translateY(-2px);
+ box-shadow: 0 8px 28px rgba(0, 0, 0, 0.2);
+}
+
+.ctaButtonIcon {
+ flex-shrink: 0;
+ display: block;
+ color: inherit;
+ transition: transform var(--motion-duration-normal) var(--motion-ease-standard);
+}
+
+.ctaPrimary:hover .ctaButtonIcon {
+ transform: translateX(4px);
}
-.ctaPrimary:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(59,130,246,0.5), 0 4px 8px rgba(0,0,0,0.12); color: white; }
-.ctaSecondary { background: rgba(15, 23, 42, 0.6); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); color: white; border: 2px solid rgba(255,255,255,0.15); }
-.ctaSecondary:hover { background: rgba(255,255,255,0.1); border-color: rgba(255,255,255,0.3); transform: translateY(-2px); color: white; }
+.ctaSecondary {
+ color: #ffffff;
+ background: rgba(255, 255, 255, 0.06);
+ border-color: rgba(255, 255, 255, 0.55);
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+}
+
+.ctaSecondary:hover {
+ background: rgba(255, 255, 255, 0.12);
+ border-color: rgba(255, 255, 255, 0.85);
+ transform: translateY(-2px);
+}
+
+.ctaButton:focus-visible {
+ outline: 2px solid #ffffff;
+ outline-offset: 3px;
+}
-.ctaButton:focus-visible,
.heroLink:focus-visible {
outline: 3px solid var(--focus-ring-color);
outline-offset: 3px;
@@ -346,7 +381,7 @@
font-size: 0.875rem;
gap: 0.375rem;
}
- .heroBannerMobile .ctaButton svg {
+ .heroBannerMobile .ctaButton .ctaButtonIcon {
width: 16px;
height: 16px;
}
@@ -384,7 +419,7 @@
font-size: 0.8125rem;
gap: 0.25rem;
}
- .heroBannerMobile .ctaButton svg {
+ .heroBannerMobile .ctaButton .ctaButtonIcon {
width: 14px;
height: 14px;
}
@@ -398,3 +433,14 @@
[data-theme='dark'] .heroWave {
color: var(--compose-bg-900);
}
+
+@media (prefers-reduced-motion: reduce) {
+ .ctaPrimary:hover,
+ .ctaSecondary:hover {
+ transform: none;
+ }
+
+ .ctaPrimary:hover .ctaButtonIcon {
+ transform: none;
+ }
+}
diff --git a/website/src/components/index.js b/website/src/components/index.js
index 77fae7c1..a59a335f 100644
--- a/website/src/components/index.js
+++ b/website/src/components/index.js
@@ -11,6 +11,7 @@ export { default as RelatedDocs } from './docs/RelatedDocs';
export { default as LastUpdated } from './docs/LastUpdated';
export { default as ReadingTime } from './docs/ReadingTime';
export { default as WasThisHelpful } from './docs/WasThisHelpful';
+export { default as DocPageAside } from './docs/DocPageAside';
export { default as StepIndicator } from './docs/StepIndicator';
// UI Components
diff --git a/website/src/components/navigation/GithubStarsNavbarItem/index.js b/website/src/components/navigation/GithubStarsNavbarItem/index.js
new file mode 100644
index 00000000..f10a0f5d
--- /dev/null
+++ b/website/src/components/navigation/GithubStarsNavbarItem/index.js
@@ -0,0 +1,80 @@
+import React from 'react';
+import clsx from 'clsx';
+import NavbarNavLink from '@theme/NavbarItem/NavbarNavLink';
+import {useGithubStarsCount} from '@site/src/hooks/useGithubStarsCount';
+import styles from './styles.module.css';
+
+function formatStarCount(n) {
+ if (typeof n !== 'number' || !Number.isFinite(n)) return null;
+ if (n < 10000) return n.toLocaleString();
+ if (n < 1_000_000) return `${(n / 1000).toFixed(n % 1000 === 0 ? 0 : 1)}k`;
+ return `${(n / 1_000_000).toFixed(1)}M`;
+}
+
+function StarGlyph({className}) {
+ return (
+
+
+
+ );
+}
+
+/**
+ * Navbar item: GitHub repo link with live star count (GitHub REST API).
+ * Use in docusaurus.config.js: { type: 'custom-githubStars', position: 'right' }.
+ */
+export default function GithubStarsNavbarItem({
+ mobile = false,
+ position: _position,
+ owner = 'Perfect-Abstractions',
+ repo = 'Compose',
+ className,
+}) {
+ const href = `https://github.com/${owner}/${repo}`;
+ const {count, isLoading} = useGithubStarsCount({owner, repo});
+
+ const label = (
+ <>
+
+
+
+ {isLoading ? '…' : formatStarCount(count) ?? '—'}
+
+
+ GitHub
+ >
+ );
+
+ const link = (
+
+ );
+
+ if (mobile) {
+ return {link} ;
+ }
+
+ return link;
+}
diff --git a/website/src/components/navigation/GithubStarsNavbarItem/styles.module.css b/website/src/components/navigation/GithubStarsNavbarItem/styles.module.css
new file mode 100644
index 00000000..b30eab31
--- /dev/null
+++ b/website/src/components/navigation/GithubStarsNavbarItem/styles.module.css
@@ -0,0 +1,115 @@
+.link {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.4rem;
+ transition:
+ transform 160ms cubic-bezier(0.23, 1, 0.32, 1),
+ color 160ms ease;
+}
+
+.link:active {
+ transform: scale(0.97);
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .link {
+ transition: color 160ms ease;
+ }
+
+ .link:active {
+ transform: none;
+ }
+
+ .badge,
+ .star {
+ transition: none;
+ }
+}
+
+.badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.3rem;
+ margin-right: 0.5rem;
+ padding: 0.12rem 0.45rem 0.12rem 0.38rem;
+ border-radius: 9999px;
+ font-size: 0.8rem;
+ font-weight: 600;
+ font-variant-numeric: tabular-nums;
+ line-height: 1.2;
+ color: #a16207;
+ background: linear-gradient(
+ 165deg,
+ rgba(253, 224, 71, 0.35) 0%,
+ rgba(251, 191, 36, 0.18) 45%,
+ rgba(245, 158, 11, 0.12) 100%
+ );
+ border: 1px solid rgba(217, 119, 6, 0.35);
+ box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4) inset;
+ transition:
+ border-color 180ms cubic-bezier(0.23, 1, 0.32, 1),
+ background 180ms cubic-bezier(0.23, 1, 0.32, 1),
+ box-shadow 180ms cubic-bezier(0.23, 1, 0.32, 1);
+}
+
+[data-theme='dark'] .badge {
+ color: #fde047;
+ background: linear-gradient(
+ 165deg,
+ rgba(253, 224, 71, 0.14) 0%,
+ rgba(245, 158, 11, 0.08) 50%,
+ rgba(180, 83, 9, 0.12) 100%
+ );
+ border-color: rgba(250, 204, 21, 0.35);
+ box-shadow: 0 1px 0 rgba(255, 255, 255, 0.06) inset;
+}
+
+.star {
+ flex-shrink: 0;
+ width: 14px;
+ height: 14px;
+ color: #ca8a04;
+ filter: drop-shadow(0 0 5px rgba(251, 191, 36, 0.35));
+ transition: color 180ms ease, filter 180ms ease;
+}
+
+[data-theme='dark'] .star {
+ color: #facc15;
+ filter: drop-shadow(0 0 6px rgba(250, 204, 21, 0.25));
+}
+
+.count {
+ min-width: 1ch;
+}
+
+@media (hover: hover) and (pointer: fine) {
+ .link:hover .badge {
+ border-color: rgba(217, 119, 6, 0.55);
+ background: linear-gradient(
+ 165deg,
+ rgba(253, 224, 71, 0.45) 0%,
+ rgba(251, 191, 36, 0.26) 45%,
+ rgba(245, 158, 11, 0.18) 100%
+ );
+ }
+
+ .link:hover .star {
+ color: #b45309;
+ filter: drop-shadow(0 0 7px rgba(251, 191, 36, 0.5));
+ }
+
+ [data-theme='dark'] .link:hover .badge {
+ border-color: rgba(253, 224, 71, 0.5);
+ background: linear-gradient(
+ 165deg,
+ rgba(253, 224, 71, 0.22) 0%,
+ rgba(245, 158, 11, 0.12) 50%,
+ rgba(180, 83, 9, 0.18) 100%
+ );
+ }
+
+ [data-theme='dark'] .link:hover .star {
+ color: #fef08a;
+ filter: drop-shadow(0 0 8px rgba(253, 224, 71, 0.35));
+ }
+}
diff --git a/website/src/css/cards.css b/website/src/css/cards.css
index 7564255b..cfe2236e 100644
--- a/website/src/css/cards.css
+++ b/website/src/css/cards.css
@@ -1,18 +1,27 @@
/**
* Card Component Styles
- * Feature cards and general card styling
+ * Feature cards — aligned with homepage FeaturesSection (.featureCard)
*/
-/* Feature cards */
+/* Feature cards (global — e.g. HomepageFeatures) */
.feature-card {
- background: var(--ifm-background-surface-color);
- border-radius: 1rem;
- padding: 2.5rem 2rem;
- height: 100%;
- border: 2px solid var(--ifm-color-emphasis-200);
- transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
+ height: 100%;
+ border-radius: 1.25rem;
+ padding: 2.5rem 2rem;
overflow: hidden;
+ isolation: isolate;
+ background: linear-gradient(155deg, #ffffff 0%, #f1f5f9 55%, #f8fafc 100%);
+ border: 1px solid rgba(15, 23, 42, 0.08);
+ box-shadow:
+ 0 0 0 1px rgba(255, 255, 255, 0.7) inset,
+ 0 1px 0 rgba(255, 255, 255, 0.9) inset,
+ 0 22px 44px -18px rgba(15, 23, 42, 0.12),
+ 0 4px 12px rgba(15, 23, 42, 0.04);
+ transition:
+ border-color var(--motion-duration-normal) var(--motion-ease-standard),
+ box-shadow var(--motion-duration-normal) var(--motion-ease-standard),
+ transform var(--motion-duration-normal) var(--motion-ease-standard);
}
.feature-card::before {
@@ -21,88 +30,170 @@
top: 0;
left: 0;
right: 0;
- height: 4px;
- background: linear-gradient(90deg, #3b82f6 0%, #60a5fa 100%);
+ height: 3px;
+ background: linear-gradient(
+ 90deg,
+ transparent 0%,
+ var(--compose-primary-500) 22%,
+ #60a5fa 55%,
+ rgba(96, 165, 250, 0.35) 100%
+ );
+ transform: scaleX(0);
+ transform-origin: left;
+ transition: transform 320ms cubic-bezier(0.22, 1, 0.36, 1);
+ z-index: 2;
+ pointer-events: none;
+}
+
+.feature-card::after {
+ content: '';
+ position: absolute;
+ inset: -40% -20% auto -20%;
+ height: 85%;
+ background: radial-gradient(closest-side, rgba(59, 130, 246, 0.14), transparent 72%);
opacity: 0;
- transition: opacity 0.3s ease;
+ transition: opacity var(--motion-duration-normal) var(--motion-ease-standard);
+ pointer-events: none;
+ z-index: 0;
}
.feature-card:hover {
- transform: translateY(-8px);
- box-shadow: 0 24px 40px -12px rgba(59, 130, 246, 0.2);
- border-color: var(--ifm-color-primary);
+ border-color: rgba(59, 130, 246, 0.28);
+ box-shadow:
+ 0 0 0 1px rgba(59, 130, 246, 0.12) inset,
+ 0 28px 56px -20px rgba(37, 99, 235, 0.18),
+ 0 12px 28px rgba(15, 23, 42, 0.08);
+ transform: translateY(-5px);
}
.feature-card:hover::before {
+ transform: scaleX(1);
+}
+
+.feature-card:hover::after {
opacity: 1;
}
.feature-card h3 {
+ position: relative;
+ z-index: 1;
font-size: 1.25rem;
- font-weight: 700;
+ font-weight: 750;
+ letter-spacing: -0.025em;
+ line-height: 1.22;
margin-top: 0.5rem;
margin-bottom: 1rem;
- color: var(--ifm-color-emphasis-900);
+ color: #0f172a;
}
.feature-card p {
- line-height: 1.7;
- color: var(--ifm-color-emphasis-700);
+ position: relative;
+ z-index: 1;
+ line-height: 1.65;
+ font-size: 0.96875rem;
+ color: #334155;
margin: 0;
}
.feature-icon {
+ position: relative;
+ z-index: 1;
font-size: 3.5rem;
line-height: 1;
display: block;
margin-bottom: 1rem;
- transition: transform 0.3s ease;
+ transition: transform var(--motion-duration-normal) var(--motion-ease-standard);
}
.feature-card:hover .feature-icon {
- transform: scale(1.1);
+ transform: translateY(-2px);
}
-/* Light mode feature cards */
-.feature-card {
- background: #ffffff;
- border: 2px solid #e2e8f0;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
+[data-theme='dark'] .feature-card {
+ background: linear-gradient(
+ 155deg,
+ rgba(30, 41, 59, 0.92) 0%,
+ rgba(15, 23, 42, 0.72) 48%,
+ rgba(10, 14, 26, 0.55) 100%
+ );
+ backdrop-filter: blur(14px);
+ -webkit-backdrop-filter: blur(14px);
+ border-color: rgba(148, 163, 184, 0.14);
+ box-shadow:
+ 0 0 0 1px rgba(0, 0, 0, 0.45) inset,
+ 0 1px 0 rgba(255, 255, 255, 0.06) inset,
+ 0 28px 56px -24px rgba(0, 0, 0, 0.65),
+ 0 0 0 1px rgba(59, 130, 246, 0.06);
+}
+
+[data-theme='dark'] .feature-card::after {
+ background: radial-gradient(closest-side, rgba(96, 165, 250, 0.22), transparent 70%);
}
-.feature-card:hover {
- border-color: #3b82f6;
- box-shadow: 0 8px 20px rgba(59, 130, 246, 0.15);
+[data-theme='dark'] .feature-card:hover {
+ border-color: rgba(96, 165, 250, 0.35);
+ box-shadow:
+ 0 0 0 1px rgba(59, 130, 246, 0.2) inset,
+ 0 1px 0 rgba(255, 255, 255, 0.09) inset,
+ 0 0 40px -8px rgba(59, 130, 246, 0.35),
+ 0 32px 64px -28px rgba(0, 0, 0, 0.75);
}
-/* Dark mode feature cards */
-[data-theme='dark'] .feature-card {
- background: #1e293b;
- border-color: #334155;
+[data-theme='dark'] .feature-card h3 {
+ color: #ffffff;
}
-[data-theme='dark'] .feature-card:hover {
- background: #334155;
- border-color: #475569;
- box-shadow: 0 24px 40px -12px rgba(96, 165, 250, 0.2);
+[data-theme='dark'] .feature-card p {
+ color: rgba(226, 232, 240, 0.82);
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .feature-card {
+ transition:
+ border-color var(--motion-duration-fast) ease,
+ box-shadow var(--motion-duration-fast) ease;
+ }
+
+ .feature-card:hover {
+ transform: none;
+ }
+
+ .feature-card::before {
+ transition: transform 0.01ms;
+ }
+
+ .feature-card:hover .feature-icon {
+ transform: none;
+ }
}
/* General cards */
[data-theme='dark'] .card {
- background: #1e293b;
- border: 1px solid #334155;
+ background: linear-gradient(
+ 155deg,
+ rgba(30, 41, 59, 0.85) 0%,
+ rgba(15, 23, 42, 0.9) 100%
+ );
+ border: 1px solid rgba(148, 163, 184, 0.14);
}
[data-theme='dark'] .card:hover {
- border-color: #475569;
+ border-color: rgba(96, 165, 250, 0.25);
}
/* Custom utility class */
.card-hover {
- transition: all 0.3s ease;
+ transition:
+ border-color var(--motion-duration-normal) var(--motion-ease-standard),
+ box-shadow var(--motion-duration-normal) var(--motion-ease-standard),
+ transform var(--motion-duration-normal) var(--motion-ease-standard);
}
.card-hover:hover {
transform: translateY(-4px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
}
+
+[data-theme='dark'] .card-hover:hover {
+ box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.45);
+}
diff --git a/website/src/css/documentation.css b/website/src/css/documentation.css
index 95c0b5a0..8751158e 100644
--- a/website/src/css/documentation.css
+++ b/website/src/css/documentation.css
@@ -307,12 +307,12 @@
}
/* ============================================
- DOCUMENT NAVIGATION (Prev/Next)
+ DOCUMENT NAVIGATION (Prev/Next) — minimal text rail
============================================ */
.pagination-nav {
- margin-top: 4rem;
- padding-top: 2rem;
+ margin-top: 1.5rem;
+ padding-top: 1.5rem;
border-top: 1px solid var(--ifm-color-emphasis-200);
}
@@ -321,50 +321,96 @@
}
.pagination-nav__link {
- border: 1px solid var(--ifm-color-emphasis-200);
- border-radius: 0.75rem;
- padding: 1.5rem;
- transition: all 0.3s ease;
- background: var(--ifm-background-surface-color);
+ border: none;
+ border-radius: 0;
+ padding: 0.25rem 0;
+ background: transparent;
+ box-shadow: none;
+ color: inherit;
+ transition: color 180ms cubic-bezier(0.23, 1, 0.32, 1);
}
-[data-theme='dark'] .pagination-nav__link {
- background: #0f0f0f;
- border-color: #2a2a2a;
+.pagination-nav__link:hover {
+ border-color: transparent;
+ box-shadow: none;
+ transform: none;
+ text-decoration: none;
}
-.pagination-nav__link:hover {
- border-color: var(--ifm-color-primary);
- transform: translateY(-2px);
- box-shadow: 0 8px 16px -4px rgba(59, 130, 246, 0.2);
+.pagination-nav__link:hover .pagination-nav__label {
+ color: var(--ifm-color-primary);
}
-[data-theme='dark'] .pagination-nav__link:hover {
- border-color: #60a5fa;
- box-shadow: 0 8px 16px -4px rgba(96, 165, 250, 0.2);
+[data-theme='dark'] .pagination-nav__link:hover .pagination-nav__label {
+ color: #93c5fd;
+}
+
+.pagination-nav__link:focus-visible {
+ outline: 2px solid var(--ifm-color-primary);
+ outline-offset: 4px;
+ border-radius: 4px;
}
.pagination-nav__sublabel {
- font-size: 0.875rem;
- text-transform: uppercase;
- letter-spacing: 0.05em;
- font-weight: 600;
- color: var(--ifm-color-emphasis-600);
- margin-bottom: 0.5rem;
+ font-size: 0.8125rem;
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 500;
+ color: var(--ifm-color-emphasis-700);
+ margin-bottom: 0.35rem;
}
[data-theme='dark'] .pagination-nav__sublabel {
- color: #909090;
+ color: #a3a3a3;
}
.pagination-nav__label {
- font-size: 1.125rem;
+ font-size: 1rem;
font-weight: 700;
+ line-height: 1.35;
color: var(--ifm-heading-color);
}
[data-theme='dark'] .pagination-nav__label {
- color: #ffffff;
+ color: #fafafa;
+}
+
+/* Single arrows; grid keeps → on first row when titles wrap (Infima uses « » inline). */
+.pagination-nav__link--prev .pagination-nav__label {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ gap: 0.35em;
+ align-items: start;
+}
+
+.pagination-nav__link--prev .pagination-nav__label::before {
+ content: '←';
+ grid-column: 1;
+ grid-row: 1;
+}
+
+.pagination-nav__link--next .pagination-nav__label {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ gap: 0.35em;
+ align-items: start;
+ text-align: right;
+}
+
+.pagination-nav__link--next .pagination-nav__label::after {
+ content: '→';
+ grid-column: 2;
+ grid-row: 1;
+}
+
+@media (hover: hover) and (pointer: fine) {
+ .pagination-nav__link:hover .pagination-nav__sublabel {
+ color: var(--ifm-color-emphasis-800);
+ }
+
+ [data-theme='dark'] .pagination-nav__link:hover .pagination-nav__sublabel {
+ color: #d4d4d4;
+ }
}
/* ============================================
diff --git a/website/src/css/footer.css b/website/src/css/footer.css
index 1c1b3c36..e26b54be 100644
--- a/website/src/css/footer.css
+++ b/website/src/css/footer.css
@@ -3,56 +3,254 @@
* Footer styling for light and dark modes
*/
-/* CSS Variables for Netlify Badge */
:root {
- --netlify-badge-color: #8EFBF7;
+ --netlify-badge-color: #8efbf7;
--netlify-badge-bg-light: rgba(255, 255, 255, 0.1);
--netlify-badge-bg-light-hover: rgba(255, 255, 255, 0.15);
--netlify-badge-bg-dark: rgba(255, 255, 255, 0.08);
--netlify-badge-bg-dark-hover: rgba(255, 255, 255, 0.12);
- --netlify-badge-spacing: 0.75rem;
- --netlify-badge-gap-reduction: -0.5rem;
+ --footer-meta-border: rgba(255, 255, 255, 0.12);
+ --ease-footer: cubic-bezier(0.23, 1, 0.32, 1);
}
-/* Light mode footer */
+/* Solid “dock” footer (avoids competing with page hero gradients) */
.footer {
- background: linear-gradient(135deg, #1e3a8a 0%, #1e293b 100%);
- color: #ffffff;
+ background: #0f172a;
+ color: #f8fafc;
position: relative;
+ border-top: 1px solid rgba(255, 255, 255, 0.08);
}
+/* Section labels: quieter than link text */
.footer__title {
- color: var(--ifm-color-primary-lighter);
+ font-size: 0.75rem;
font-weight: 700;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: #94a3b8;
+ margin-bottom: 1rem;
+}
+
+.footer__link-item {
+ color: #e2e8f0;
+ transition: color 160ms var(--ease-footer);
+}
+
+@media (hover: hover) and (pointer: fine) {
+ .footer__link-item:hover {
+ color: #93c5fd;
+ }
+}
+
+.footer__item {
+ color: #cbd5e1;
+}
+
+/* Space between link grid and meta row */
+.footer__links {
+ margin-bottom: 2.5rem;
+ padding-bottom: 0.5rem;
+}
+
+/* Mobile: collapsible footer columns (native ) */
+.footer__links--accordion .footer__accordionSection {
+ width: 100%;
+ max-width: 100%;
+ flex: 0 0 100%;
+ margin-bottom: 0;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
+}
+
+.footer__links--accordion .footer__accordionSection:last-child {
+ border-bottom: none;
+ margin-bottom: 0;
+}
+
+.footer__accordionSummary {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ min-height: 2.75rem;
+ padding-block: 0.35rem;
+ cursor: pointer;
+ list-style: none;
+ transition: transform 160ms var(--ease-footer);
+ margin-bottom: 1rem;
+}
+
+.footer__accordionSummary::-webkit-details-marker {
+ display: none;
+}
+
+.footer__accordionSummary:active {
+ transform: scale(0.99);
+}
+
+.footer__accordionSummary:focus-visible {
+ outline: 2px solid #93c5fd;
+ outline-offset: 2px;
+ border-radius: 4px;
+}
+
+/* Title shares a row with the chevron — no bottom margin (spacing lives on .footer__accordionSection) */
+.footer__links--accordion .footer__accordionSummary .footer__title {
+ margin-bottom: 0;
+}
+
+.footer__accordionTitle {
+ text-align: start;
+}
+
+.footer__accordionChevron {
+ display: block;
+ width: 0.45rem;
+ height: 0.45rem;
+ flex-shrink: 0;
+ border-right: 2px solid #94a3b8;
+ border-bottom: 2px solid #94a3b8;
+ transform: rotate(-45deg);
+ transition: transform 180ms var(--ease-footer);
+}
+
+.footer__links--accordion .footer__accordionSection[open] .footer__accordionChevron {
+ transform: rotate(45deg);
+}
+
+.footer__accordionList {
+ margin: 0 0 0.75rem;
+ padding: 0.15rem 0 0;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .footer__accordionChevron {
+ transition: none;
+ }
+
+ .footer__accordionSummary {
+ transition: none;
+ }
+
+ .footer__accordionSummary:active {
+ transform: none;
+ }
+}
+
+[data-theme='dark'] .footer__links--accordion .footer__accordionSection {
+ border-bottom-color: rgba(148, 163, 184, 0.2);
+}
+
+[data-theme='dark'] .footer__accordionChevron {
+ border-right-color: #64748b;
+ border-bottom-color: #64748b;
+}
+
+.footer__bottom {
+ margin-top: 0;
+ padding-top: 2rem;
+ border-top: 1px solid var(--footer-meta-border);
+}
+
+.footer__metaRow {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 1.25rem;
+}
+
+@media (min-width: 997px) {
+ .footer__metaRow {
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1.5rem;
+ }
+}
+
+/* Wraps ; inner .footer__copyright comes from the theme component */
+.footer__copyrightCol {
+ min-width: 0;
+ width: 100%;
+ text-align: center;
+}
+
+@media (min-width: 997px) {
+ .footer__copyrightCol {
+ width: auto;
+ max-width: min(100%, 42rem);
+ text-align: left;
+ }
+}
+
+.footer__copyrightCol .footer__copyright,
+.footer__copyright {
+ font-size: 0.9rem;
+ line-height: 1.6;
+ color: #94a3b8;
+}
+
+.footer__copyrightCol .footer__copyright a,
+.footer__copyright a {
+ color: #93c5fd;
+ transition: color 160ms var(--ease-footer);
+}
+
+@media (hover: hover) and (pointer: fine) {
+ .footer__copyrightCol .footer__copyright a:hover,
+ .footer__copyright a:hover {
+ color: #bfdbfe;
+ }
}
-/* Dark mode footer */
[data-theme='dark'] .footer {
background: #0a0e1a;
- border-top: 1px solid #1e293b;
+ border-top-color: #1e293b;
}
[data-theme='dark'] .footer__title {
- color: #ffffff;
+ color: #64748b;
}
[data-theme='dark'] .footer__item {
- color: #909090;
+ color: #94a3b8;
+}
+
+[data-theme='dark'] .footer__link-item {
+ color: #cbd5e1;
+}
+
+@media (hover: hover) and (pointer: fine) {
+ [data-theme='dark'] .footer__link-item:hover {
+ color: #60a5fa;
+ }
}
-[data-theme='dark'] .footer__link-item:hover {
+[data-theme='dark'] .footer__copyrightCol .footer__copyright,
+[data-theme='dark'] .footer__copyright {
+ color: #64748b;
+}
+
+[data-theme='dark'] .footer__copyrightCol .footer__copyright a,
+[data-theme='dark'] .footer__copyright a {
color: #60a5fa;
}
-/* ============================================
- NETLIFY BADGE STYLES
- ============================================ */
+@media (hover: hover) and (pointer: fine) {
+ [data-theme='dark'] .footer__copyrightCol .footer__copyright a:hover,
+ [data-theme='dark'] .footer__copyright a:hover {
+ color: #93c5fd;
+ }
+}
+
+[data-theme='dark'] .footer__bottom {
+ border-top-color: rgba(148, 163, 184, 0.2);
+}
+/* Netlify badge — in document flow with copyright */
.netlifyBadge {
- position: absolute;
- bottom: 20px;
- right: 20px;
- z-index: 10;
+ position: static;
+ flex-shrink: 0;
}
.netlifyBadge a {
@@ -67,19 +265,29 @@
text-decoration: none;
font-size: 0.875rem;
font-weight: 500;
- transition: all 0.2s ease;
+ transition:
+ background-color 160ms var(--ease-footer),
+ box-shadow 160ms var(--ease-footer),
+ transform 160ms var(--ease-footer),
+ border-color 160ms var(--ease-footer);
box-shadow: 0 4px 12px rgba(142, 251, 247, 0.2), 0 2px 4px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(12px) saturate(180%);
-webkit-backdrop-filter: blur(12px) saturate(180%);
}
-.netlifyBadge a:hover {
- background: var(--netlify-badge-bg-light-hover);
- border-color: var(--netlify-badge-color);
- transform: translateY(-1px);
- box-shadow: 0 6px 16px rgba(142, 251, 247, 0.3), 0 2px 6px rgba(0, 0, 0, 0.15);
- text-decoration: none;
- color: #ffffff;
+@media (hover: hover) and (pointer: fine) {
+ .netlifyBadge a:hover {
+ background: var(--netlify-badge-bg-light-hover);
+ border-color: var(--netlify-badge-color);
+ transform: translateY(-1px);
+ box-shadow: 0 6px 16px rgba(142, 251, 247, 0.3), 0 2px 6px rgba(0, 0, 0, 0.15);
+ text-decoration: none;
+ color: #ffffff;
+ }
+}
+
+.netlifyBadge a:active {
+ transform: scale(0.98);
}
.netlifyBadge .badgeDot {
@@ -100,91 +308,39 @@
color: var(--netlify-badge-color);
}
-/* Dark mode badge adjustments - keep glassmorphism */
[data-theme='dark'] .netlifyBadge a {
background: var(--netlify-badge-bg-dark);
border: 1px solid var(--netlify-badge-color);
}
-[data-theme='dark'] .netlifyBadge a:hover {
- background: var(--netlify-badge-bg-dark-hover);
- border-color: var(--netlify-badge-color);
+@media (hover: hover) and (pointer: fine) {
+ [data-theme='dark'] .netlifyBadge a:hover {
+ background: var(--netlify-badge-bg-dark-hover);
+ border-color: var(--netlify-badge-color);
+ }
}
-/* ============================================
- RESPONSIVE STYLES
- ============================================ */
-
-/* Mobile: Badge appears below copyright inside footer */
@media (max-width: 996px) {
- /* Footer spacing reset */
+ .footer__links {
+ margin-bottom: 0 !important;
+ padding-bottom: 0 !important;
+ }
+
.footer__bottom {
- margin-bottom: 0;
- padding-bottom: 0;
- }
-
- .footer {
- padding-bottom: 0;
- margin-bottom: 0;
- }
-
- /* Badge positioning for mobile */
- .footer ~ .netlifyBadge,
- .netlifyBadge {
- position: static;
- bottom: auto;
- right: auto;
- margin-top: var(--netlify-badge-gap-reduction);
- margin-bottom: 0;
- text-align: center;
- padding-top: 0;
- padding-bottom: var(--netlify-badge-spacing);
- padding-left: 1rem;
- padding-right: 1rem;
- border-top: none;
- width: 100%;
- background: linear-gradient(135deg, #1e3a8a 0%, #1e293b 100%);
- }
-
- .footer ~ .netlifyBadge {
- margin-bottom: 0;
- }
-
- [data-theme='dark'] .footer ~ .netlifyBadge,
- [data-theme='dark'] .netlifyBadge {
- background: #0a0e1a;
- }
-
- .netlifyBadge a {
- padding: 8px 16px;
- font-size: 0.875rem;
- display: inline-flex;
+ padding-top: 1.75rem;
}
-
- .netlifyBadge .badgeDot {
- width: 8px;
- height: 8px;
+
+ .footer__metaRow {
+ gap: 1rem;
}
}
@media (max-width: 768px) {
- .footer {
- padding-bottom: 0;
- margin-bottom: 0;
- }
-
- .netlifyBadge {
- margin-top: var(--netlify-badge-gap-reduction);
- margin-bottom: 0;
- padding-top: 0;
- padding-bottom: var(--netlify-badge-spacing);
- }
-
.netlifyBadge a {
padding: 6px 12px;
font-size: 0.75rem;
}
-
+
.netlifyBadge .badgeDot {
width: 6px;
height: 6px;
@@ -192,29 +348,17 @@
}
@media (max-width: 480px) {
- .footer {
- padding-bottom: 0;
- margin-bottom: 0;
- }
-
- .netlifyBadge {
- margin-top: var(--netlify-badge-gap-reduction);
- margin-bottom: 0;
- padding-top: 0;
- padding-bottom: var(--netlify-badge-spacing);
- }
-
.netlifyBadge .badgeText {
display: inline;
}
-
+
.netlifyBadge a {
padding: 6px 12px;
font-size: 0.75rem;
}
-
+
.netlifyBadge .badgeDot {
width: 6px;
height: 6px;
}
-}
\ No newline at end of file
+}
diff --git a/website/src/css/navbar.css b/website/src/css/navbar.css
index 6e30a894..ddcd37b5 100644
--- a/website/src/css/navbar.css
+++ b/website/src/css/navbar.css
@@ -338,18 +338,33 @@
/* Mobile sidebar header */
.navbar-sidebar__brand {
- background: linear-gradient(135deg, #3b82f6 0%, #1e40af 100%);
+ background: #f1f5f9;
padding: 1.25rem 1rem;
- box-shadow: 0 2px 10px rgba(59, 130, 246, 0.2);
+ border-bottom: 1px solid #e2e8f0;
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
}
[data-theme='dark'] .navbar-sidebar__brand {
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
+ border-bottom-color: rgba(148, 163, 184, 0.15);
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
}
+ /* Match main navbar wordmark (gradient) on light gray; solid light on dark header */
.navbar-sidebar__brand .navbar__title {
- color: white !important;
- -webkit-text-fill-color: white !important;
+ background: linear-gradient(135deg, #3b82f6 0%, #1e40af 100%);
+ -webkit-background-clip: text;
+ background-clip: text;
+ -webkit-text-fill-color: transparent;
+ color: transparent;
+ }
+
+ [data-theme='dark'] .navbar-sidebar__brand .navbar__title {
+ background: none;
+ -webkit-background-clip: unset;
+ background-clip: unset;
+ color: #f8fafc !important;
+ -webkit-text-fill-color: #f8fafc !important;
}
/* Mobile menu links */
diff --git a/website/src/css/pagination.css b/website/src/css/pagination.css
index 6c11d020..cade5d40 100644
--- a/website/src/css/pagination.css
+++ b/website/src/css/pagination.css
@@ -1,30 +1,4 @@
/**
- * Pagination Component Styles
- * Prev/Next pagination navigation styling
+ * Pagination — doc prev/next styling lives in documentation.css (minimal rail).
+ * Blog/list paginators share .pagination-nav; no extra card overrides here.
*/
-
-/* Light mode pagination */
-.pagination-nav__link {
- background: #ffffff;
- border: 1px solid #e2e8f0;
- color: #1e293b;
-}
-
-.pagination-nav__link:hover {
- background: #f8fafc;
- border-color: #3b82f6;
- color: #0f172a;
-}
-
-/* Dark mode pagination */
-[data-theme='dark'] .pagination-nav__link {
- background: #1e293b;
- border: 1px solid #334155;
- color: #e0e0e0;
-}
-
-[data-theme='dark'] .pagination-nav__link:hover {
- background: #334155;
- border-color: #475569;
- color: #ffffff;
-}
\ No newline at end of file
diff --git a/website/src/hooks/useMatchMedia.js b/website/src/hooks/useMatchMedia.js
new file mode 100644
index 00000000..f493dbce
--- /dev/null
+++ b/website/src/hooks/useMatchMedia.js
@@ -0,0 +1,19 @@
+import { useState, useEffect } from 'react';
+
+/**
+ * SSR-safe matchMedia: defaults to false until the client runs the query.
+ * @param {string} query - e.g. '(max-width: 1100px)'
+ */
+export function useMatchMedia(query) {
+ const [matches, setMatches] = useState(false);
+
+ useEffect(() => {
+ const mql = window.matchMedia(query);
+ const update = () => setMatches(mql.matches);
+ update();
+ mql.addEventListener('change', update);
+ return () => mql.removeEventListener('change', update);
+ }, [query]);
+
+ return matches;
+}
diff --git a/website/src/theme/CodeBlock/Buttons/CopyButton/index.js b/website/src/theme/CodeBlock/Buttons/CopyButton/index.js
new file mode 100644
index 00000000..089467a2
--- /dev/null
+++ b/website/src/theme/CodeBlock/Buttons/CopyButton/index.js
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+import React, { useCallback, useState, useRef, useEffect } from 'react';
+import clsx from 'clsx';
+import { translate } from '@docusaurus/Translate';
+import { useCodeBlockContext } from '@docusaurus/theme-common/internal';
+import Button from '@theme/CodeBlock/Buttons/Button';
+import IconCopy from '@theme/Icon/Copy';
+import IconSuccess from '@theme/Icon/Success';
+import { captureInstallCommandCopyIfTracked } from '@site/src/utils/captureInstallCommandCopy';
+import styles from './styles.module.css';
+
+function title() {
+ return translate({
+ id: 'theme.CodeBlock.copy',
+ message: 'Copy',
+ description: 'The copy button label on code blocks',
+ });
+}
+
+function ariaLabel(isCopied) {
+ return isCopied
+ ? translate({
+ id: 'theme.CodeBlock.copied',
+ message: 'Copied',
+ description: 'The copied button label on code blocks',
+ })
+ : translate({
+ id: 'theme.CodeBlock.copyButtonAriaLabel',
+ message: 'Copy code to clipboard',
+ description: 'The ARIA label for the copy code blocks button',
+ });
+}
+
+function useCopyButton() {
+ const {
+ metadata: { code },
+ } = useCodeBlockContext();
+ const [isCopied, setIsCopied] = useState(false);
+ const copyTimeout = useRef(undefined);
+ const copyCode = useCallback(() => {
+ navigator.clipboard.writeText(code).then(() => {
+ captureInstallCommandCopyIfTracked(code);
+ setIsCopied(true);
+ copyTimeout.current = window.setTimeout(() => {
+ setIsCopied(false);
+ }, 1000);
+ });
+ }, [code]);
+ useEffect(() => () => window.clearTimeout(copyTimeout.current), []);
+ return { copyCode, isCopied };
+}
+
+export default function CopyButton({ className }) {
+ const { copyCode, isCopied } = useCopyButton();
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/website/src/theme/CodeBlock/Buttons/CopyButton/styles.module.css b/website/src/theme/CodeBlock/Buttons/CopyButton/styles.module.css
new file mode 100644
index 00000000..c9e0ac5a
--- /dev/null
+++ b/website/src/theme/CodeBlock/Buttons/CopyButton/styles.module.css
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+:global(.theme-code-block:hover) .copyButtonCopied {
+ opacity: 1 !important;
+}
+
+.copyButtonIcons {
+ position: relative;
+ width: 1.125rem;
+ height: 1.125rem;
+}
+
+.copyButtonIcon,
+.copyButtonSuccessIcon {
+ position: absolute;
+ top: 0;
+ left: 0;
+ fill: currentColor;
+ opacity: inherit;
+ width: inherit;
+ height: inherit;
+ transition: all var(--ifm-transition-fast) ease;
+}
+
+.copyButtonSuccessIcon {
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%) scale(0.33);
+ opacity: 0;
+ color: #00d600;
+}
+
+.copyButtonCopied .copyButtonIcon {
+ transform: scale(0.33);
+ opacity: 0;
+}
+
+.copyButtonCopied .copyButtonSuccessIcon {
+ transform: translate(-50%, -50%) scale(1);
+ opacity: 1;
+ transition-delay: 0.075s;
+}
diff --git a/website/src/theme/DocItem/Layout/index.js b/website/src/theme/DocItem/Layout/index.js
new file mode 100644
index 00000000..456528b3
--- /dev/null
+++ b/website/src/theme/DocItem/Layout/index.js
@@ -0,0 +1,91 @@
+/**
+ * Swizzled DocItem Layout: doc feedback aside (Cloudflare-style rail + mobile inline).
+ */
+import React from 'react';
+import clsx from 'clsx';
+import { useWindowSize } from '@docusaurus/theme-common';
+import { useDoc } from '@docusaurus/plugin-content-docs/client';
+import DocItemPaginator from '@theme/DocItem/Paginator';
+import DocVersionBanner from '@theme/DocVersionBanner';
+import DocVersionBadge from '@theme/DocVersionBadge';
+import DocItemFooter from '@theme/DocItem/Footer';
+import DocItemTOCMobile from '@theme/DocItem/TOC/Mobile';
+import DocItemTOCDesktop from '@theme/DocItem/TOC/Desktop';
+import DocItemContent from '@theme/DocItem/Content';
+import DocBreadcrumbs from '@theme/DocBreadcrumbs';
+import ContentVisibility from '@theme/ContentVisibility';
+import DocPageAside from '@site/src/components/docs/DocPageAside';
+
+import styles from './styles.module.css';
+
+function useDocTOC() {
+ const { frontMatter, toc } = useDoc();
+ const windowSize = useWindowSize();
+
+ const hidden = frontMatter.hide_table_of_contents;
+ const canRender = !hidden && toc.length > 0;
+
+ const mobile = canRender ? : undefined;
+
+ const desktop =
+ canRender && (windowSize === 'desktop' || windowSize === 'ssr') ? (
+
+ ) : undefined;
+
+ return {
+ hidden,
+ mobile,
+ desktop,
+ };
+}
+
+export default function DocItemLayout({ children }) {
+ const docTOC = useDocTOC();
+ const { metadata } = useDoc();
+ const windowSize = useWindowSize();
+ const isDesktop = windowSize === 'desktop' || windowSize === 'ssr';
+ const showDesktopRightColumn = isDesktop;
+ const showAsideInline = !isDesktop;
+
+ return (
+
+
+
+
+
+
+
+
+ {docTOC.mobile}
+ {children}
+
+
+ {showAsideInline &&
}
+
+
+
+ {showDesktopRightColumn && (
+
+ {docTOC.desktop ? (
+
+
+ {docTOC.desktop}
+
+
+
+
+
+ ) : (
+
+
+
+ )}
+
+ )}
+
+ );
+}
diff --git a/website/src/theme/DocItem/Layout/styles.module.css b/website/src/theme/DocItem/Layout/styles.module.css
new file mode 100644
index 00000000..85e5557e
--- /dev/null
+++ b/website/src/theme/DocItem/Layout/styles.module.css
@@ -0,0 +1,61 @@
+/**
+ * DocItem Layout (swizzled from theme-classic)
+ */
+.docItemContainer header + *,
+.docItemContainer article > *:first-child {
+ margin-top: 0;
+}
+
+@media (min-width: 997px) {
+ .docItemCol {
+ max-width: 75% !important;
+ }
+
+ /**
+ * Sticky column: TOC scrolls in a capped region; feedback sits below (no overlap).
+ * Do not use flex: 1 on the scroll wrapper when the rail only has max-height — with
+ * height:auto the flex item collapses to 0 and the TOC vanishes.
+ */
+ .docTocRail {
+ --doc-toc-footer-slot: 13.5rem;
+ position: sticky;
+ top: calc(var(--ifm-navbar-height) + 1rem);
+ align-self: flex-start;
+ display: flex;
+ flex-direction: column;
+ gap: 0;
+ max-height: calc(100vh - var(--ifm-navbar-height) - 2rem);
+ width: 100%;
+ }
+
+ .docTocRailScroll {
+ flex: 0 1 auto;
+ max-height: calc(
+ 100vh - var(--ifm-navbar-height) - 2rem - var(--doc-toc-footer-slot)
+ );
+ min-height: 0;
+ overflow-x: hidden;
+ overflow-y: auto;
+ }
+
+ /* Rail owns stickiness + outer scroll; reset theme TOC sticky/max-height */
+ .docTocRailScroll :global(.theme-doc-toc-desktop) {
+ position: static !important;
+ max-height: none !important;
+ top: auto !important;
+ overflow: visible !important;
+ }
+
+ .docTocRailFooter {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ /* Feedback + links when there is no TOC (still use right column) */
+ .docAsideOnly {
+ position: sticky;
+ top: calc(var(--ifm-navbar-height) + 1rem);
+ align-self: flex-start;
+ width: 100%;
+ }
+}
diff --git a/website/src/theme/Footer/Layout/index.js b/website/src/theme/Footer/Layout/index.js
new file mode 100644
index 00000000..2433d827
--- /dev/null
+++ b/website/src/theme/Footer/Layout/index.js
@@ -0,0 +1,31 @@
+/**
+ * Footer layout — bottom meta row pairs copyright with Netlify attribution (OSS plan).
+ */
+import React from 'react';
+import clsx from 'clsx';
+import {ThemeClassNames} from '@docusaurus/theme-common';
+import NetlifyFooterBadge from '../../../components/footer/NetlifyFooterBadge';
+
+export default function FooterLayout({style, links, logo, copyright}) {
+ return (
+
+ );
+}
diff --git a/website/src/theme/Footer/Links/MultiColumn/index.js b/website/src/theme/Footer/Links/MultiColumn/index.js
new file mode 100644
index 00000000..a73e502e
--- /dev/null
+++ b/website/src/theme/Footer/Links/MultiColumn/index.js
@@ -0,0 +1,72 @@
+/**
+ * Footer link columns — desktop: classic grid; narrow viewports: collapsible sections.
+ */
+import React, { useLayoutEffect, useState } from 'react';
+import clsx from 'clsx';
+import { ThemeClassNames } from '@docusaurus/theme-common';
+import LinkItem from '@theme/Footer/LinkItem';
+import OriginalFooterLinksMultiColumn from '@theme-original/Footer/Links/MultiColumn';
+
+const MOBILE_FOOTER_MQ = '(max-width: 996px)';
+
+function ColumnLinkItem({ item }) {
+ return item.html ? (
+
+ ) : (
+
+
+
+ );
+}
+
+function FooterLinksAccordion({ columns }) {
+ return (
+
+ {columns.map((column, i) => (
+
+
+ {column.title}
+
+
+
+ {column.items.map((item, j) => (
+
+ ))}
+
+
+ ))}
+
+ );
+}
+
+export default function FooterLinksMultiColumn({ columns }) {
+ const [{ mounted, isMobile }, setLayout] = useState({
+ mounted: false,
+ isMobile: false,
+ });
+
+ useLayoutEffect(() => {
+ const mql = window.matchMedia(MOBILE_FOOTER_MQ);
+ setLayout({ mounted: true, isMobile: mql.matches });
+ const onChange = () =>
+ setLayout((prev) => ({ ...prev, isMobile: mql.matches }));
+ mql.addEventListener('change', onChange);
+ return () => mql.removeEventListener('change', onChange);
+ }, []);
+
+ if (!mounted || !isMobile) {
+ return ;
+ }
+
+ return ;
+}
diff --git a/website/src/theme/Footer/index.js b/website/src/theme/Footer/index.js
index 74201420..0350a1db 100644
--- a/website/src/theme/Footer/index.js
+++ b/website/src/theme/Footer/index.js
@@ -20,18 +20,6 @@ export default function FooterWrapper(props) {
-
);
}
diff --git a/website/src/theme/Navbar/Content/index.js b/website/src/theme/Navbar/Content/index.js
index c36c4e42..99bcd92d 100644
--- a/website/src/theme/Navbar/Content/index.js
+++ b/website/src/theme/Navbar/Content/index.js
@@ -18,6 +18,9 @@ import NavbarSearch from '@theme/Navbar/Search';
import NavbarGradient from '@site/src/components/navigation/NavbarGradient';
import styles from './styles.module.css';
+const SECURITY_OVERVIEW_HREF =
+ 'https://github.com/Perfect-Abstractions/Compose?tab=security-ov-file';
+
function useNavbarItems() {
return useThemeConfig().navbar.items;
}
@@ -66,11 +69,16 @@ export default function NavbarContent() {
<>
{!mobileSidebar.disabled &&