From 8d6a9df99a739b76416c33f47a7a7590aa58a945 Mon Sep 17 00:00:00 2001 From: maxnorm Date: Sat, 4 Apr 2026 12:36:30 -0400 Subject: [PATCH 1/6] update cards style, reorder sidebar --- website/docs/design/index.mdx | 5 - website/docs/facets/authentication.mdx | 261 -------------- website/docs/facets/facets-and-modules.mdx | 337 ------------------ website/docs/foundations/index.mdx | 6 - website/docs/intro.mdx | 10 +- website/sidebars.js | 17 +- website/src/components/docs/DocCard/index.js | 2 +- .../components/docs/DocCard/styles.module.css | 287 +++++++++++---- .../docs/RelatedDocs/styles.module.css | 202 +++++++++-- .../features/FeatureCard/styles.module.css | 194 ++++++++-- .../features/FeatureGrid/styles.module.css | 199 +++++++---- .../src/components/home/FeaturesSection.js | 6 +- website/src/css/cards.css | 171 ++++++--- 13 files changed, 834 insertions(+), 863 deletions(-) delete mode 100644 website/docs/facets/authentication.mdx delete mode 100644 website/docs/facets/facets-and-modules.mdx diff --git a/website/docs/design/index.mdx b/website/docs/design/index.mdx index 64c8af64..437a6904 100644 --- a/website/docs/design/index.mdx +++ b/website/docs/design/index.mdx @@ -18,35 +18,30 @@ This section contains the guidelines and rules for developing new facets and Sol title="Compose Is Written to Be Read" description="Emphasizes clarity first: keep facets and libraries self-contained, ordered top-to-bottom, and easy to read. Avoid clever abstractions that reduce readability." href="/docs/design/written-to-be-read" - icon={} size="medium" /> } size="medium" /> } size="medium" /> } size="medium" /> } size="medium" /> diff --git a/website/docs/facets/authentication.mdx b/website/docs/facets/authentication.mdx deleted file mode 100644 index 76739e98..00000000 --- a/website/docs/facets/authentication.mdx +++ /dev/null @@ -1,261 +0,0 @@ ---- -sidebar_position: 2 -title: Authentication -description: Access control and permission management for diamond contracts. Implement role-based access control (RBAC) and secure your Compose facets. -draft: true ---- - -import DocHero from '@site/src/components/docs/DocHero'; -import Callout from '@site/src/components/ui/Callout'; -import APIReference from '@site/src/components/api/APIReference'; -import FeatureGrid, { FeatureGridItem } from '@site/src/components/features/FeatureGrid'; - - - -## Overview - -Authentication in Compose is handled through composable facets that implement role-based access control (RBAC). The system is designed to be flexible, secure, and easy to integrate with your custom facets. - - -Authentication facets use the same shared storage pattern as all Compose components, allowing both standard and custom facets to check permissions consistently. - - -## Core Features - - - } - title="Role-Based Access" - description="Define custom roles with specific permissions for different user types." - /> - } - title="Flexible Permissions" - description="Grant and revoke permissions dynamically without redeployment." - /> - } - title="Secure by Default" - description="Built-in checks and modifiers to prevent unauthorized access." - /> - - -## Access Control Facet - -The `AccessControlFacet` provides standard role-based access control functionality: - -### Key Functions - - - - - - - -## Usage Example - -Here's how to integrate access control in your custom facet: - -```solidity -import {AccessControlMod} from "compose/AccessControlMod.sol"; - -contract AdminFacet { - // Define your custom role - bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); - - function adminOnlyFunction() external { - // Check if caller has admin role - require( - LibAccessControl.hasRole(ADMIN_ROLE, msg.sender), - "AccessControl: caller is not admin" - ); - - // Your admin logic here - performAdminAction(); - } - - function performAdminAction() internal { - // Implementation - } -} -``` - - -Always define role constants using `keccak256` hashes of descriptive names. This makes your code more readable and prevents typos. - - -## Role Hierarchy - -Compose supports hierarchical roles where certain roles can manage other roles: - -```solidity -// Setup role hierarchy -bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; -bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); -bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); - -// DEFAULT_ADMIN_ROLE can grant/revoke MINTER_ROLE and BURNER_ROLE -// MINTER_ROLE can only mint -// BURNER_ROLE can only burn -``` - -## Integration with Custom Facets - -Your custom facets can use `AccessControlMod` to check permissions: - -```solidity -import {AccessControlMod} from "compose/AccessControlMod.sol"; -import {ERC20Mod} from "compose/ERC20Mod.sol"; - -contract TokenMinterFacet { - bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - - function mintTokens(address to, uint256 amount) external { - // Check minter permission - require( - AccessControlMod.hasRole(MINTER_ROLE, msg.sender), - "TokenMinter: caller is not minter" - ); - - // Mint using Compose's ERC20 library - ERC20Mod.mint(to, amount); - } -} -``` - - -The `AccessControlMod` library accesses the same storage as `AccessControlFacet`, so permissions set through the facet are instantly available to your custom facets! - - -## Security Considerations - - -Always protect role management functions. The `DEFAULT_ADMIN_ROLE` has ultimate control over all roles. - - -### Best Practices - -1. **Use Role Modifiers**: Create reusable modifiers for role checks -2. **Minimize Admin Privileges**: Grant only necessary permissions -3. **Audit Role Changes**: Emit events for all role grants/revokes -4. **Test Thoroughly**: Verify access control in all scenarios -5. **Document Roles**: Clearly document what each role can do - -## Common Patterns - -### Multi-Signature Admin - -```solidity -// Use a multi-sig wallet as the admin role holder -address multiSigWallet = 0x...; -LibAccessControl.grantRole(DEFAULT_ADMIN_ROLE, multiSigWallet); -``` - -### Time-Locked Roles - -```solidity -contract TimeLockFacet { - bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); - - mapping(address => uint256) public roleExpiry; - - function grantTemporaryRole( - address account, - uint256 duration - ) external { - require( - AccessControlMod.hasRole(DEFAULT_ADMIN_ROLE, msg.sender), - "Not admin" - ); - - AccessControlMod.grantRole(OPERATOR_ROLE, account); - roleExpiry[account] = block.timestamp + duration; - } - - function revokeExpiredRoles(address account) external { - if (block.timestamp >= roleExpiry[account]) { - AccessControlMod.revokeRole(OPERATOR_ROLE, account); - } - } -} -``` - -## Next Steps - - - } - title="Facets & Modules" - description="Learn how authentication integrates with the facet architecture." - href="/docs/foundations/facets-and-modules" - /> - } - title="Security Best Practices" - description="Explore comprehensive security patterns for your contracts." - href="/docs/" - /> - - diff --git a/website/docs/facets/facets-and-modules.mdx b/website/docs/facets/facets-and-modules.mdx deleted file mode 100644 index e4856b10..00000000 --- a/website/docs/facets/facets-and-modules.mdx +++ /dev/null @@ -1,337 +0,0 @@ ---- -sidebar_position: 3 -title: Facets & Modules -description: How facets and modules work together through shared storage. When to use facets vs modules and how to build composable smart contract systems. -draft: true ---- - -import DocHero from '@site/src/components/docs/DocHero'; -import Callout from '@site/src/components/ui/Callout'; -import FeatureGrid, { FeatureGridItem } from '@site/src/components/features/FeatureGrid'; -import DocCard, { DocCardGrid } from '@site/src/components/docs/DocCard'; - - - -## Overview - -Compose uses a dual-component architecture where **facets** provide complete implementations and **modules** provide reusable helper functions. Both work with the same shared storage, enabling seamless integration. - - -Facets are complete implementations. Modules are helper functions. Both access the **same storage**. - - -## Architecture Comparison - -
- -| Component | Purpose | Use Case | -|-----------|---------|----------| -| **Facets** | Complete, self-contained implementations | Use as-is in your diamond for standard functionality | -| **Modules** | Helper functions for custom facets | Import into your custom facets to extend Compose | - -
- -## Facets: Complete Implementations - -Facets are fully functional smart contracts that can be added to your diamond: - - - } - title="Self-Contained" - description="Complete implementations ready to use out of the box." - /> - } - title="Plug & Play" - description="Add to your diamond via the diamond cut function." - /> - } - title="Verified" - description="(In the future) Audited, tested, and deployed on multiple chains." - /> - - -### Example: ERC721Facet - -```solidity -// ERC721Facet provides standard NFT functionality -contract ERC721Facet { - function balanceOf(address owner) external view returns (uint256) { - return ERC721Mod.balanceOf(owner); - } - - function ownerOf(uint256 tokenId) external view returns (address) { - return ERC721Mod.ownerOf(tokenId); - } - - function transferFrom( - address from, - address to, - uint256 tokenId - ) external { - ERC721Mod.transferFrom(from, to, tokenId); - } - - // ... more standard ERC721 functions -} -``` - - -You can add `ERC721Facet` to your diamond and immediately have full ERC721 functionality! - - -## Modules: Helper Functions - -Modules provide internal functions that your custom facets can use: - - - } - title="Building Blocks" - description="Reusable functions for your custom logic." - /> - } - title="Shared Storage" - description="Access the same data as standard facets." - /> - } - title="Flexible" - description="Combine with your custom logic however you need." - /> - - -### Example: ERC721Mod - -```solidity -// ERC721Mod provides helper functions for custom facets -library ERC721Mod { - function mint(address to, uint256 tokenId) internal { - // Modifies storage that ERC721Facet reads - ERC721Storage storage s = erc721Storage(); - s.owners[tokenId] = to; - s.balances[to]++; - // ... emit events, etc. - } - - function burn(uint256 tokenId) internal { - // Modifies same storage - ERC721Storage storage s = erc721Storage(); - address owner = s.owners[tokenId]; - delete s.owners[tokenId]; - s.balances[owner]--; - // ... emit events, etc. - } - - // ... more helper functions -} -``` - -## The Magic: Shared Storage - -Both facets and modules access the **same storage location** in your diamond: - -```solidity -// Your custom facet -import {ERC721Mod} from "compose/ERC721Mod.sol"; - -contract GameNFTFacet { - function mintWithGameLogic( - address player, - uint256 tokenId - ) external { - // Your custom game logic - require( - playerHasEnoughPoints(player), - "Not enough points" - ); - - // Use ERC721Mod to mint - // This modifies the SAME storage that - // ERC721Facet uses! - ERC721Mod.mint(player, tokenId); - - // Now ERC721Facet.ownerOf(tokenId) - // returns player! - updatePlayerStats(player); - } -} -``` - - -You don't need to inherit from anything. You don't need to override functions. Just use the module functions and everything works together! - - -## Visual Diagram - -``` -┌────────────────────────────────────────┐ -│ Diamond Contract │ -├────────────────────────────────────────┤ -│ │ -│ ┌──────────────┐ ┌───────────────┐ │ -│ │ ERC721Facet │ │ GameNFTFacet │ │ -│ │ │ │ │ │ -│ │ - balanceOf()│ │ - mint with │ │ -│ │ - ownerOf() │ │ game logic │ │ -│ │ - transfer() │ │ │ │ -│ └──────┬───────┘ └───────┬───────┘ │ -│ │ │ │ -│ │ ┌─────────────────┘ │ -│ ▼ ▼ │ -│ ┌─────────────┐ │ -│ │ ERC721Mod │ │ -│ └──────┬──────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────┐ │ -│ │ Storage │ │ -│ │ (Shared) │ │ -│ └─────────────┘ │ -│ │ -└────────────────────────────────────────┘ -``` - -## When to Use Each - - - } - title="Use Facets When..." - description="You want standard, complete functionality with no customization needed." - > -
    -
  • Implementing standard token interfaces
  • -
  • Adding access control
  • -
  • Using governance systems
  • -
-
- } - title="Use Modules When..." - description="You're building custom facets that need to integrate with Compose." - > -
    -
  • Creating custom minting logic
  • -
  • Building game mechanics
  • -
  • Implementing custom rules
  • -
-
-
- -## Real-World Example - -Let's build a game where players earn NFTs by completing quests: - -```solidity -import {ERC721Mod} from "compose/ERC721Mod.sol"; -import {AccessControlMod} from "compose/AccessControlMod.sol"; - -contract QuestRewardsFacet { - bytes32 public constant GAME_MASTER = keccak256("GAME_MASTER"); - - mapping(uint256 => bool) public questCompleted; - - function completeQuest( - address player, - uint256 questId, - uint256 tokenId - ) external { - // Use AccessControlMod to check permissions - require( - AccessControlMod.hasRole(GAME_MASTER, msg.sender), - "Not authorized" - ); - - // Your game logic - require(!questCompleted[questId], "Quest already claimed"); - questCompleted[questId] = true; - - // Use ERC721Mod to mint reward NFT - ERC721Mod.mint(player, tokenId); - - // The standard ERC721Facet.ownerOf(tokenId) now works! - // The player can transfer using ERC721Facet.transferFrom()! - } -} -``` - - -Your `QuestRewardsFacet` mints NFTs that work with all standard ERC721 functions. No inheritance, no complexity! - - -## Best Practices - - - } - title="Do: Use Modules in Custom Facets" - description="Import and use module functions to integrate with Compose functionality." - /> - } - title="Do: Add Standard Facets As-Is" - description="Use complete facets when you don't need customization." - /> - } - title="Don't: Modify Standard Facets" - description="Create custom facets instead. Keep standard facets unchanged." - /> - } - title="Don't: Call Facets from Facets" - description="Use modules for facet-to-facet communication, not external calls." - /> - - -## Available Facets & Modules - - - } - title="ERC20" - description="Fungible token standard with mint, burn, and transfer functionality." - /> - } - title="ERC721" - description="Non-fungible token (NFT) standard with full metadata support." - /> - } - title="ERC1155" - description="Multi-token standard supporting both fungible and non-fungible tokens." - /> - } - title="Access Control" - description="Role-based permission system for managing contract access." - /> - - -## Next Steps - - - } - title="Storage Patterns" - description="Deep dive into how shared storage works under the hood." - href="/docs/" - /> - } - title="Quick Start" - description="Build your first diamond with facets and modules." - href="/docs/getting-started/quick-start" - /> - - diff --git a/website/docs/foundations/index.mdx b/website/docs/foundations/index.mdx index 4859a2e3..f4a41da1 100644 --- a/website/docs/foundations/index.mdx +++ b/website/docs/foundations/index.mdx @@ -20,42 +20,36 @@ import CalloutBox from '@site/src/components/ui/CalloutBox'; title="Diamond Smart Contracts" description="A diamond contract is made up of multiple parts. It exists at one address and holds all storage, but uses separate smart contracts called facets to provide its functionality." href="/docs/foundations/diamond-contracts" - icon={} size="medium" /> } size="medium" /> } size="medium" /> } size="medium" /> } size="medium" /> } size="medium" /> diff --git a/website/docs/intro.mdx b/website/docs/intro.mdx index 768a2a3a..8b9deec5 100644 --- a/website/docs/intro.mdx +++ b/website/docs/intro.mdx @@ -26,34 +26,28 @@ That's **Compose** — where your unique value and functionality connects seamle ## Why Choose Compose? - + } title="On-chain Standard Library" description="(Not yet, in the future) - Access verified, audited smart contracts deployed once and reused across multiple diamonds on multiple blockchains." /> } title="Composable Architecture" description="Mix and match on-chain components to build exactly what you need." /> } title="Readability First" description="Code designed to be understood. Compose prioritizes clarity over cleverness." /> } title="Upgradeable by Design" description="Full power of ERC-2535 Diamonds means your contracts can evolve without full redeployment." /> } title="Battle-Tested Patterns" description="Community-reviewed implementations following proven best practices and security standards." /> } title="Developer Experience" description="Intuitive APIs, comprehensive documentation, and helpful libraries make development a breeze." /> @@ -88,14 +82,12 @@ Our smart contract library is perfect for: } href="/docs/foundations" size="large" /> } href="/docs/design" size="large" /> diff --git a/website/sidebars.js b/website/sidebars.js index f8f0990b..a6326118 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -20,7 +20,7 @@ const sidebars = { { type: 'category', label: 'Getting Started', - collapsed: true, + collapsed: false, items: [ { type: 'autogenerated', @@ -46,7 +46,7 @@ const sidebars = { { type: 'category', label: 'Design', - collapsed: false, + collapsed: true, link: { type: 'doc', id: 'design/index', @@ -58,19 +58,6 @@ const sidebars = { }, ], }, - /* - { - type: 'category', - label: 'Facets', - collapsed: true, - items: [ - { - type: 'autogenerated', - dirName: 'facets', - }, - ], - }, - */ { type: 'category', label: 'Contribution', diff --git a/website/src/components/docs/DocCard/index.js b/website/src/components/docs/DocCard/index.js index a8072213..3c52d4a0 100644 --- a/website/src/components/docs/DocCard/index.js +++ b/website/src/components/docs/DocCard/index.js @@ -5,7 +5,7 @@ import Icon from '../../ui/Icon'; import styles from './styles.module.css'; /** - * DocCard Component - MoneyKit-inspired documentation card + * DocCard * * @param {string} title - Card title * @param {string} description - Card description diff --git a/website/src/components/docs/DocCard/styles.module.css b/website/src/components/docs/DocCard/styles.module.css index 9b245175..355a40c8 100644 --- a/website/src/components/docs/DocCard/styles.module.css +++ b/website/src/components/docs/DocCard/styles.module.css @@ -1,20 +1,36 @@ -/* DocCard - MoneyKit-inspired card component */ +/* DocCard — matches homepage feature cards (featuresSection) */ + .docCard { display: flex; align-items: center; gap: 1.25rem; 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; position: relative; overflow: hidden; + isolation: isolate; cursor: pointer; -} - + 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); +} + +.docCard:focus-visible { + outline: 3px solid var(--focus-ring-color); + outline-offset: 3px; +} + +/* Facet highlight sweep (top edge) */ .docCard::before { content: ''; position: absolute; @@ -22,34 +38,81 @@ 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; +} + +/* Radial bloom on hover */ +.docCard::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; } .docCard:hover { - transform: translateY(-4px); - border-color: var(--ifm-color-primary); - box-shadow: 0 12px 24px -6px rgba(59, 130, 246, 0.25); + 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); } .docCard:hover::before { + transform: scaleX(1); +} + +.docCard:hover::after { opacity: 1; } -/* Dark theme */ [data-theme='dark'] .docCard { - background: #141414; - border-color: #2a2a2a; + 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'] .docCard::after { + background: radial-gradient(closest-side, rgba(96, 165, 250, 0.22), transparent 70%); } [data-theme='dark'] .docCard:hover { - background: #1a1a1a; - border-color: #60a5fa; - box-shadow: 0 12px 24px -6px rgba(96, 165, 250, 0.25); + 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); } -/* Card Icon */ +/* Card Icon — aligned with code showcase feature icons */ .docCardIcon { flex-shrink: 0; display: flex; @@ -58,16 +121,21 @@ width: 3.5rem; height: 3.5rem; border-radius: 0.75rem; - background: linear-gradient(135deg, var(--ifm-color-primary-lightest) 0%, var(--ifm-color-primary-lighter) 100%); - transition: transform 0.3s ease; + position: relative; + z-index: 1; + 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'] .docCardIcon { - background: linear-gradient(135deg, rgba(96, 165, 250, 0.15) 0%, rgba(59, 130, 246, 0.15) 100%); -} - -.docCard:hover .docCardIcon { - transform: scale(1.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; } .iconEmoji { @@ -79,51 +147,86 @@ .docCardContent { flex: 1; min-width: 0; + position: relative; + z-index: 1; } .docCardTitle { margin: 0 0 0.5rem 0; font-size: 1.125rem; - font-weight: 700; - color: var(--ifm-heading-color); - letter-spacing: -0.01em; + font-weight: 750; + letter-spacing: -0.025em; + line-height: 1.22; + color: #0f172a; } .docCardDescription { 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; +} + +[data-theme='dark'] .docCardTitle { + color: #ffffff; } [data-theme='dark'] .docCardDescription { - color: #a0a0a0; + color: rgba(226, 232, 240, 0.82); } -/* Card Arrow */ +/* Arrow — homepage “hint” color logic */ .docCardArrow { flex-shrink: 0; display: flex; align-items: center; justify-content: center; - color: var(--ifm-color-emphasis-500); - transition: all 0.3s ease; + position: relative; + z-index: 1; + color: #1e40af; + transition: + color var(--motion-duration-normal) var(--motion-ease-standard), + transform var(--motion-duration-normal) var(--motion-ease-standard); +} + +[data-theme='dark'] .docCardArrow { + color: #93c5fd; + opacity: 0.5; } .docCard:hover .docCardArrow { - color: var(--ifm-color-primary); - transform: translateX(4px); + color: #1d4ed8; + transform: translateX(5px); } [data-theme='dark'] .docCard:hover .docCardArrow { - color: #60a5fa; + color: #4189f7; + opacity: 0.95; } -/* Card Variants */ +/* Primary variant */ .docCard--primary { - background: linear-gradient(135deg, var(--ifm-color-primary) 0%, var(--ifm-color-primary-dark) 100%); - border-color: var(--ifm-color-primary); + background: linear-gradient(135deg, var(--compose-primary-500) 0%, var(--compose-primary-600) 100%); + border-color: var(--compose-primary-500); color: white; + box-shadow: + 0 0 0 1px rgba(255, 255, 255, 0.12) inset, + 0 22px 44px -18px rgba(37, 99, 235, 0.45), + 0 4px 14px rgba(59, 130, 246, 0.35); +} + +.docCard--primary::before { + background: linear-gradient( + 90deg, + transparent 0%, + rgba(255, 255, 255, 0.85) 35%, + rgba(255, 255, 255, 0.45) 70%, + transparent 100% + ); +} + +.docCard--primary::after { + background: radial-gradient(closest-side, rgba(255, 255, 255, 0.2), transparent 70%); } .docCard--primary .docCardTitle, @@ -132,29 +235,58 @@ color: white; } +.docCard--primary .docCardArrow { + opacity: 1; +} + .docCard--primary .docCardIcon { background: rgba(255, 255, 255, 0.2); - backdrop-filter: blur(10px); + border-color: rgba(255, 255, 255, 0.35); + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.25) inset; + color: white; } .docCard--primary:hover { - transform: translateY(-4px); - box-shadow: 0 16px 32px -8px rgba(59, 130, 246, 0.4); + border-color: rgba(255, 255, 255, 0.45); + box-shadow: + 0 0 0 1px rgba(255, 255, 255, 0.18) inset, + 0 32px 64px -20px rgba(37, 99, 235, 0.55), + 0 12px 28px rgba(15, 23, 42, 0.2); } +.docCard--primary:hover .docCardArrow { + color: #ffffff; +} + +/* Secondary — slightly stronger edge, same family as default */ .docCard--secondary { - border: 2px solid var(--ifm-color-emphasis-300); + border-color: rgba(15, 23, 42, 0.14); + box-shadow: + 0 0 0 1px rgba(255, 255, 255, 0.65) inset, + 0 1px 0 rgba(255, 255, 255, 0.85) inset, + 0 18px 36px -16px rgba(15, 23, 42, 0.14), + 0 3px 10px rgba(15, 23, 42, 0.05); +} + +[data-theme='dark'] .docCard--secondary { + border-color: rgba(148, 163, 184, 0.22); +} + +.docCard--secondary:hover { + border-color: rgba(59, 130, 246, 0.32); } /* Card Sizes */ .docCard--small { padding: 1.25rem 1.5rem; gap: 1rem; + border-radius: 1.125rem; } .docCard--small .docCardIcon { width: 2.5rem; height: 2.5rem; + border-radius: 0.625rem; } .docCard--small .iconEmoji { @@ -163,10 +295,11 @@ .docCard--small .docCardTitle { font-size: 1rem; + font-weight: 700; } .docCard--small .docCardDescription { - font-size: 0.875rem; + font-size: 0.9375rem; } .docCard--large { @@ -184,38 +317,35 @@ } .docCard--large .docCardTitle { - font-size: 1.5rem; + font-size: 1.3125rem; } -/* DocCardGrid */ +/* DocCardGrid — match homepage features grid rhythm */ .docCardGrid { display: grid; grid-template-columns: repeat(var(--grid-columns, 2), 1fr); - gap: 1.5rem; + gap: 2rem; margin: 2rem 0; } -/* When sidebar is visible (typical desktop width), adjust for better fit */ @media (min-width: 997px) and (max-width: 1200px) { .docCardGrid { grid-template-columns: 1fr; - gap: 1.25rem; + gap: 1.5rem; } } -/* Wider screens with sidebar, allow 2 columns if space permits */ @media (min-width: 1201px) and (max-width: 1400px) { .docCardGrid { grid-template-columns: repeat(var(--grid-columns, 2), 1fr); - gap: 1.25rem; + gap: 1.5rem; } } -/* Very wide screens, allow configured columns */ @media (min-width: 1401px) { .docCardGrid { grid-template-columns: repeat(var(--grid-columns, 2), 1fr); - gap: 1.5rem; + gap: 2rem; } } @@ -226,13 +356,12 @@ } } -/* Responsive adjustments for cards when sidebar is visible */ @media (min-width: 997px) and (max-width: 1200px) { .docCard { padding: 1.75rem; gap: 1.125rem; } - + .docCardIcon { width: 3.25rem; height: 3.25rem; @@ -241,16 +370,50 @@ @media (max-width: 768px) { .docCard { - padding: 1.5rem; + padding: 1.75rem 1.5rem 2rem; gap: 1rem; } - + .docCardIcon { width: 3rem; height: 3rem; } - + .iconEmoji { font-size: 1.75rem; } -} \ No newline at end of file + + .docCardTitle { + font-size: 1.1875rem; + } + + .docCardDescription { + font-size: 0.9375rem; + } +} + +@media (prefers-reduced-motion: reduce) { + .docCard { + transition: + border-color var(--motion-duration-fast) ease, + box-shadow var(--motion-duration-fast) ease; + } + + .docCard:hover { + transform: none; + } + + .docCard::before { + transition: transform 0.01ms; + } + + .docCardArrow { + transition: + color var(--motion-duration-fast) ease, + transform 0.01ms; + } + + .docCard:hover .docCardArrow { + transform: none; + } +} diff --git a/website/src/components/docs/RelatedDocs/styles.module.css b/website/src/components/docs/RelatedDocs/styles.module.css index 0860610b..6e8817d5 100644 --- a/website/src/components/docs/RelatedDocs/styles.module.css +++ b/website/src/components/docs/RelatedDocs/styles.module.css @@ -1,20 +1,26 @@ -/* RelatedDocs Component */ +/* RelatedDocs — section + cards match homepage / DocCard language */ + .relatedDocs { margin: 3rem 0; padding: 2rem; - background: var(--ifm-background-surface-color); - border: 1px solid var(--ifm-color-emphasis-200); - border-radius: 0.75rem; + border-radius: 1.25rem; + background: linear-gradient(155deg, #f8fafc 0%, #f1f5f9 100%); + border: 1px solid rgba(15, 23, 42, 0.08); } [data-theme='dark'] .relatedDocs { - background: #0f0f0f; - border-color: #2a2a2a; + background: linear-gradient( + 155deg, + rgba(30, 41, 59, 0.5) 0%, + rgba(15, 23, 42, 0.65) 100% + ); + border-color: rgba(148, 163, 184, 0.14); } .relatedDocsTitle { font-size: 1.5rem; - font-weight: 700; + font-weight: 750; + letter-spacing: -0.02em; margin-bottom: 1.5rem; color: var(--ifm-heading-color); } @@ -26,82 +32,193 @@ } .relatedDocCard { + position: relative; display: flex; align-items: flex-start; gap: 1rem; - padding: 1.25rem; - background: var(--ifm-background-color); - border: 1px solid var(--ifm-color-emphasis-200); - border-radius: 0.5rem; + padding: 1.25rem 1.35rem; text-decoration: none; color: inherit; - transition: all 0.2s ease; + overflow: hidden; + isolation: isolate; + border-radius: 1.125rem; + 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 14px 32px -16px rgba(15, 23, 42, 0.1); + 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); } -[data-theme='dark'] .relatedDocCard { - background: #0a0a0a; - border-color: #2a2a2a; +.relatedDocCard:focus-visible { + outline: 3px solid var(--focus-ring-color); + outline-offset: 2px; +} + +.relatedDocCard::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + 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; +} + +.relatedDocCard::after { + content: ''; + position: absolute; + inset: -50% -30% auto -30%; + height: 90%; + background: radial-gradient(closest-side, rgba(59, 130, 246, 0.12), transparent 72%); + opacity: 0; + transition: opacity var(--motion-duration-normal) var(--motion-ease-standard); + pointer-events: none; + z-index: 0; } .relatedDocCard:hover { - border-color: var(--ifm-color-primary); - box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15); - transform: translateY(-2px); + border-color: rgba(59, 130, 246, 0.28); + box-shadow: + 0 0 0 1px rgba(59, 130, 246, 0.1) inset, + 0 20px 40px -18px rgba(37, 99, 235, 0.16), + 0 8px 20px rgba(15, 23, 42, 0.06); + transform: translateY(-4px); text-decoration: none; color: inherit; } +.relatedDocCard:hover::before { + transform: scaleX(1); +} + +.relatedDocCard:hover::after { + opacity: 1; +} + +[data-theme='dark'] .relatedDocCard { + 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(12px); + -webkit-backdrop-filter: blur(12px); + 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 20px 44px -22px rgba(0, 0, 0, 0.55); +} + +[data-theme='dark'] .relatedDocCard::after { + background: radial-gradient(closest-side, rgba(96, 165, 250, 0.18), transparent 70%); +} + [data-theme='dark'] .relatedDocCard:hover { - border-color: #60a5fa; - box-shadow: 0 4px 12px rgba(96, 165, 250, 0.2); + border-color: rgba(96, 165, 250, 0.35); + box-shadow: + 0 0 0 1px rgba(59, 130, 246, 0.18) inset, + 0 1px 0 rgba(255, 255, 255, 0.08) inset, + 0 0 32px -6px rgba(59, 130, 246, 0.28), + 0 24px 48px -24px rgba(0, 0, 0, 0.65); } .relatedDocIcon { - font-size: 1.5rem; + position: relative; + z-index: 1; flex-shrink: 0; + width: 2.75rem; + height: 2.75rem; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.35rem; line-height: 1; + border-radius: 0.625rem; + 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; +} + +[data-theme='dark'] .relatedDocIcon { + 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; } .relatedDocContent { + position: relative; + z-index: 1; flex: 1; min-width: 0; } .relatedDocTitle { font-size: 1rem; - font-weight: 600; + font-weight: 700; + letter-spacing: -0.02em; margin: 0 0 0.5rem 0; - color: var(--ifm-heading-color); - line-height: 1.4; + color: #0f172a; + line-height: 1.35; } .relatedDocDescription { font-size: 0.875rem; - color: var(--ifm-color-emphasis-600); + line-height: 1.55; + color: #334155; margin: 0; - line-height: 1.5; +} + +[data-theme='dark'] .relatedDocTitle { + color: #ffffff; } [data-theme='dark'] .relatedDocDescription { - color: #909090; + color: rgba(226, 232, 240, 0.78); } .relatedDocArrow { + position: relative; + z-index: 1; flex-shrink: 0; - color: var(--ifm-color-emphasis-400); - transition: transform 0.2s ease, color 0.2s ease; + color: #1e40af; + transition: + color var(--motion-duration-normal) var(--motion-ease-standard), + transform var(--motion-duration-normal) var(--motion-ease-standard); +} + +[data-theme='dark'] .relatedDocArrow { + color: #93c5fd; + opacity: 0.5; } .relatedDocCard:hover .relatedDocArrow { - transform: translateX(4px); - color: var(--ifm-color-primary); + transform: translateX(5px); + color: #1d4ed8; } [data-theme='dark'] .relatedDocCard:hover .relatedDocArrow { - color: #60a5fa; + color: #4189f7; + opacity: 0.95; } -/* Responsive */ @media (max-width: 996px) { .relatedDocsGrid { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); @@ -118,11 +235,26 @@ } .relatedDocCard { - padding: 1rem; + padding: 1.1rem 1.2rem; } } +@media (prefers-reduced-motion: reduce) { + .relatedDocCard { + transition: + border-color var(--motion-duration-fast) ease, + box-shadow var(--motion-duration-fast) ease; + } + .relatedDocCard:hover { + transform: none; + } + .relatedDocCard::before { + transition: transform 0.01ms; + } - + .relatedDocCard:hover .relatedDocArrow { + transform: none; + } +} 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/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/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); +} From 0a84aedabe31598d7a918ab5ce0a53ab9b267169 Mon Sep 17 00:00:00 2001 From: maxnorm Date: Sat, 4 Apr 2026 13:12:03 -0400 Subject: [PATCH 2/6] remove header badge, redirect nav badge to security.md --- SECURITY.md | 9 +- website/docs/getting-started/installation.md | 11 ++- website/src/components/home/CtaSection.js | 2 +- website/src/components/home/HomepageHeader.js | 36 ++++---- .../src/components/home/ctaSection.module.css | 2 +- .../components/home/homepageHeader.module.css | 86 ++++++++++++++----- website/src/theme/Navbar/Content/index.js | 4 +- 7 files changed, 100 insertions(+), 50 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index e3067f13..32cc869a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,12 +4,19 @@ If you discover a security vulnerability, report it PRIVATELY with the maintaine ## Supported Versions -Compose is currently under active development.
We actively support these versions: +Compose is currently under active development. It is **NOT production ready.** and we may still make breaking changes to any part of the codebase. + +
**We actively support these versions:** | Version | Supported | | ------- | ------------------ | | latest (main) | :white_check_mark: | +for both packages: + +- [`@perfect-abstractions/compose`](https://www.npmjs.com/package/@perfect-abstractions/compose) +- [`@perfect-abstractions/compose-cli`](https://www.npmjs.com/package/@perfect-abstractions/compose-cli) + ## Reporting a Vulnerability Emails:
diff --git a/website/docs/getting-started/installation.md b/website/docs/getting-started/installation.md index 4dcbe6f2..c4f9dc3d 100644 --- a/website/docs/getting-started/installation.md +++ b/website/docs/getting-started/installation.md @@ -17,7 +17,7 @@ Compose is published as two npm packages: ## Prerequisites -- **Node.js >= 20**: required for the CLI and for npm-based Hardhat workflows +- **[Node.js](https://nodejs.org/) >= 20**: required for the CLI and for npm-based Hardhat workflows - **[Foundry](https://book.getfoundry.sh/getting-started/installation) (Optional)**: required if you use a Foundry scaffold or integrate Solidity with Forge - **Git**: used by Foundry dependency installs inside generated projects @@ -38,7 +38,7 @@ npm install -g @perfect-abstractions/compose-cli compose init ``` -## Add Compose to an existing project (Manual Installation) +## Add Compose to an existing project ### Foundry @@ -64,13 +64,12 @@ npm install @perfect-abstractions/compose import {DiamondMod} from "@perfect-abstractions/compose/diamond/DiamondMod.sol"; ``` - +- **[Core Concepts](/docs/foundations)** - Learn about facets, libraries, and shared storage +- **[Explore Available Contracts](https://github.com/Perfect-Abstractions/Compose/tree/main/src)** - See what else you can add ## Getting Help diff --git a/website/src/components/home/CtaSection.js b/website/src/components/home/CtaSection.js index 43904d10..19b2e781 100644 --- a/website/src/components/home/CtaSection.js +++ b/website/src/components/home/CtaSection.js @@ -19,7 +19,7 @@ export default function CtaSection() { Ready to build with Compose?

- 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/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
-
- - - GitHub - - - - Join Discord - - - - Contribute - -
); 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/theme/Navbar/Content/index.js b/website/src/theme/Navbar/Content/index.js index c36c4e42..68560458 100644 --- a/website/src/theme/Navbar/Content/index.js +++ b/website/src/theme/Navbar/Content/index.js @@ -67,10 +67,12 @@ export default function NavbarContent() { {!mobileSidebar.disabled && } {/* Status Badge */} +
- Early Stage + Early Stage Development
+
} From fe940781b64723f9592b7b1666e5060f5aa51cad Mon Sep 17 00:00:00 2001 From: maxnorm Date: Sat, 4 Apr 2026 14:18:57 -0400 Subject: [PATCH 3/6] Implement feedback components and GitHub stars display in navbar - Added `DocPageAside` component for displaying feedback options and report issue link. - Introduced `WasThisHelpful` component with card and aside variants for user feedback collection. - Created `GithubStarsNavbarItem` to show live GitHub stars count in the navbar. - Updated Docusaurus configuration to include a custom report issue URL. - Enhanced documentation for feedback components and updated styles for better UX. --- .../documentation/all-components.mdx | 28 +- website/docusaurus.config.js | 12 +- .../src/components/docs/DocPageAside/index.js | 47 +++ .../docs/DocPageAside/styles.module.css | 71 ++++ .../components/docs/WasThisHelpful/index.js | 235 +++++++++-- .../docs/WasThisHelpful/styles.module.css | 365 ++++++++++++++++-- website/src/components/index.js | 1 + .../navigation/GithubStarsNavbarItem/index.js | 80 ++++ .../GithubStarsNavbarItem/styles.module.css | 115 ++++++ website/src/css/documentation.css | 100 +++-- website/src/css/pagination.css | 30 +- .../CodeBlock/Buttons/CopyButton/index.js | 77 ++++ .../Buttons/CopyButton/styles.module.css | 47 +++ website/src/theme/DocItem/Layout/index.js | 91 +++++ .../theme/DocItem/Layout/styles.module.css | 61 +++ .../src/theme/NavbarItem/ComponentTypes.js | 7 + website/src/theme/NavbarItem/index.js | 22 ++ website/src/utils/captureDocsFeedback.js | 54 +++ .../src/utils/captureInstallCommandCopy.js | 46 +++ website/src/utils/telemetry/index.js | 5 + website/src/utils/telemetry/posthog.js | 43 +++ website/static/icons/thumbs-down-dark.svg | 9 + website/static/icons/thumbs-down-outline.svg | 3 - website/static/icons/thumbs-down.svg | 18 +- website/static/icons/thumbs-up-dark.svg | 9 + website/static/icons/thumbs-up-outline.svg | 3 - website/static/icons/thumbs-up.svg | 18 +- 27 files changed, 1442 insertions(+), 155 deletions(-) create mode 100644 website/src/components/docs/DocPageAside/index.js create mode 100644 website/src/components/docs/DocPageAside/styles.module.css create mode 100644 website/src/components/navigation/GithubStarsNavbarItem/index.js create mode 100644 website/src/components/navigation/GithubStarsNavbarItem/styles.module.css create mode 100644 website/src/theme/CodeBlock/Buttons/CopyButton/index.js create mode 100644 website/src/theme/CodeBlock/Buttons/CopyButton/styles.module.css create mode 100644 website/src/theme/DocItem/Layout/index.js create mode 100644 website/src/theme/DocItem/Layout/styles.module.css create mode 100644 website/src/theme/NavbarItem/ComponentTypes.js create mode 100644 website/src/theme/NavbarItem/index.js create mode 100644 website/src/utils/captureDocsFeedback.js create mode 100644 website/src/utils/captureInstallCommandCopy.js create mode 100644 website/src/utils/telemetry/index.js create mode 100644 website/src/utils/telemetry/posthog.js create mode 100644 website/static/icons/thumbs-down-dark.svg delete mode 100644 website/static/icons/thumbs-down-outline.svg create mode 100644 website/static/icons/thumbs-up-dark.svg delete mode 100644 website/static/icons/thumbs-up-outline.svg diff --git a/website/docs/contribution/documentation/all-components.mdx b/website/docs/contribution/documentation/all-components.mdx index e4ecf566..f4f21864 100644 --- a/website/docs/contribution/documentation/all-components.mdx +++ b/website/docs/contribution/documentation/all-components.mdx @@ -1766,13 +1766,14 @@ Smooth counting animation for numbers. ### WasThisHelpful -Feedback widget for collecting user feedback on documentation pages. +Feedback widget for documentation pages. The **`aside`** variant is injected automatically at the bottom of the doc TOC column on desktop (and inline above the page pager on mobile) via the swizzled doc layout. Use the **`card`** variant when you want an embedded demo in MDX. Feedback #### Live Example console.log(feedback)} /> @@ -1782,8 +1783,11 @@ Feedback widget for collecting user feedback on documentation pages. @@ -1791,9 +1795,10 @@ Feedback widget for collecting user feedback on documentation pages. ```jsx { - // Handle feedback + // Optional: PostHog events docs_helpful_vote / docs_helpful_submit still fire console.log(feedback); }} /> @@ -1801,17 +1806,14 @@ Feedback widget for collecting user feedback on documentation pages. #### Best Practices -- Place at bottom of pages -- Use unique pageId per page -- Integrate with analytics -- Respond to feedback +- Prefer the built-in global aside on real docs; use `variant="card"` only for examples +- Optional `pageId` helps PostHog filters; global aside uses permalink and title from the doc +- Events: `docs_helpful_vote`, `docs_helpful_submit` (when PostHog is enabled) #### When to Use -- End of documentation pages -- After tutorials -- Product documentation -- Help articles +- `variant="card"` in MDX examples or special pages without the global doc layout +- Most real docs rely on the built-in aside (no manual component needed) --- @@ -2209,6 +2211,4 @@ import Icon from '@site/src/components/ui/Icon'; --- - - \ No newline at end of file diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 1d00f2c4..a4d0dcf3 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -35,6 +35,11 @@ const config = { // For GitHub pages deployment, it is often '//' baseUrl: '/', + customFields: { + reportIssueUrl: + process.env.DOC_REPORT_ISSUE_URL || + 'https://github.com/Perfect-Abstractions/Compose/issues/new/choose', + }, // Broken link handling onBrokenLinks: 'throw', @@ -193,13 +198,12 @@ const config = { // ], // }, { - href: 'https://discord.gg/DCBD2UKbxc', - label: 'Discord', + type: 'custom-githubStars', position: 'right', }, { - href: 'https://github.com/Perfect-Abstractions/Compose', - label: 'GitHub', + href: 'https://discord.gg/DCBD2UKbxc', + label: 'Discord', position: 'right', }, ], diff --git a/website/src/components/docs/DocPageAside/index.js b/website/src/components/docs/DocPageAside/index.js new file mode 100644 index 00000000..cf79c429 --- /dev/null +++ b/website/src/components/docs/DocPageAside/index.js @@ -0,0 +1,47 @@ +import React from 'react'; +import clsx from 'clsx'; +import { useDoc } from '@docusaurus/plugin-content-docs/client'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import EditThisPage from '@theme/EditThisPage'; +import Link from '@docusaurus/Link'; +import WasThisHelpful from '@site/src/components/docs/WasThisHelpful'; +import Icon from '@site/src/components/ui/Icon'; +import styles from './styles.module.css'; + +/** + * @param {boolean} [soloInSidebar] — top of right column with no TOC above; drop extra top rule + */ +export default function DocPageAside({ soloInSidebar = false }) { + const { metadata } = useDoc(); + const { editUrl, permalink, title } = metadata; + const { siteConfig } = useDocusaurusContext(); + const reportIssueUrl = + siteConfig.customFields?.reportIssueUrl ?? + 'https://github.com/Perfect-Abstractions/Compose/issues/new/choose'; + + const reportIssueLink = ( + + + Report issue + + ); + + return ( + + ); +} diff --git a/website/src/components/docs/DocPageAside/styles.module.css b/website/src/components/docs/DocPageAside/styles.module.css new file mode 100644 index 00000000..74715c44 --- /dev/null +++ b/website/src/components/docs/DocPageAside/styles.module.css @@ -0,0 +1,71 @@ +.aside { + margin-top: 1.25rem; + padding-top: 1.25rem; + border-top: 1px solid var(--ifm-color-emphasis-200); +} + +/* Right column with no TOC above: avoid double separator at rail top */ +.asideSoloInRail { + margin-top: 0; + padding-top: 0; + border-top: none; +} + +[data-theme='dark'] .aside { + border-top-color: var(--ifm-color-emphasis-300); +} + +.links { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.625rem; + margin-top: 1rem; +} + +.editWrap :global(a) { + display: inline-flex; + align-items: center; + gap: 0.375rem; + font-size: 0.8125rem; + font-weight: 500; + color: var(--ifm-color-emphasis-700); + text-decoration: none; +} + +.editWrap :global(a:hover) { + color: var(--ifm-color-primary); + text-decoration: none; +} + +[data-theme='dark'] .editWrap :global(a) { + color: var(--ifm-color-emphasis-600); +} + +.reportLink { + display: inline-flex; + align-items: center; + gap: 0.375rem; + font-size: 0.8125rem; + font-weight: 500; + color: var(--ifm-color-emphasis-700); + text-decoration: none; +} + +.reportLink:hover { + color: var(--ifm-color-primary); + text-decoration: none; +} + +[data-theme='dark'] .reportLink { + color: var(--ifm-color-emphasis-600); +} + +@media (hover: hover) and (pointer: fine) { + .editWrap :global(a), + .reportLink { + transition: + color 160ms cubic-bezier(0.23, 1, 0.32, 1), + opacity 160ms cubic-bezier(0.23, 1, 0.32, 1); + } +} diff --git a/website/src/components/docs/WasThisHelpful/index.js b/website/src/components/docs/WasThisHelpful/index.js index 35f8454b..c8ae4655 100644 --- a/website/src/components/docs/WasThisHelpful/index.js +++ b/website/src/components/docs/WasThisHelpful/index.js @@ -1,41 +1,211 @@ import React, { useState } from 'react'; import clsx from 'clsx'; +import { useColorMode } from '@docusaurus/theme-common'; import Icon from '../../ui/Icon'; +import { + captureDocsHelpfulSubmit, + captureDocsHelpfulVote, +} from '@site/src/utils/captureDocsFeedback'; import styles from './styles.module.css'; +/** Same path as static/icons/checkmark-stroke.svg; inline so currentColor matches success text (img ignores it). */ +function FeedbackCheckIcon({ size = 20, className }) { + return ( + + + + ); +} + /** - * WasThisHelpful Component - Feedback widget for documentation pages - * - * @param {string} pageId - Unique identifier for the page - * @param {Function} onSubmit - Callback function when feedback is submitted + * @param {string} [pageId] + * @param {string} [permalink] + * @param {string} [title] + * @param {'card'|'aside'} [variant] + * @param {Function} [onSubmit] + * @param {import('react').ReactNode} [asideEndSlot] — e.g. Report issue; beside Yes/No on mobile; desktop: below buttons, or below textarea+Submit when “No” */ -export default function WasThisHelpful({ +export default function WasThisHelpful({ pageId, - onSubmit + permalink, + title, + onSubmit, + variant = 'card', + asideEndSlot, }) { + const { colorMode } = useColorMode(); const [feedback, setFeedback] = useState(null); const [comment, setComment] = useState(''); const [submitted, setSubmitted] = useState(false); - const handleFeedback = (value) => { + const thumbUpName = colorMode === 'dark' ? 'thumbs-up-dark' : 'thumbs-up'; + const thumbDownName = colorMode === 'dark' ? 'thumbs-down-dark' : 'thumbs-down'; + + const analyticsContext = { pageId, permalink, title, variant }; + + const handleCardFeedback = (value) => { setFeedback(value); + captureDocsHelpfulVote({ helpful: value, ...analyticsContext }); }; - const handleSubmit = () => { + const handleCardSubmit = () => { if (onSubmit) { onSubmit({ pageId, feedback, comment }); - } else { - // Default behavior - could log to analytics - console.log('Feedback submitted:', { pageId, feedback, comment }); + } + if (feedback) { + captureDocsHelpfulSubmit({ + helpful: feedback, + comment: comment.trim(), + ...analyticsContext, + }); + } + setSubmitted(true); + }; + + const handleAsideYes = () => { + captureDocsHelpfulSubmit({ + helpful: 'yes', + comment: '', + ...analyticsContext, + }); + if (onSubmit) { + onSubmit({ pageId, feedback: 'yes', comment: '' }); + } + setSubmitted(true); + }; + + const handleAsideNo = () => { + setFeedback('no'); + captureDocsHelpfulVote({ helpful: 'no', ...analyticsContext }); + }; + + const handleAsideSubmit = () => { + captureDocsHelpfulSubmit({ + helpful: 'no', + comment: comment.trim(), + ...analyticsContext, + }); + if (onSubmit) { + onSubmit({ pageId, feedback: 'no', comment: comment.trim() }); } setSubmitted(true); }; if (submitted) { - return ( -
- + const submittedBody = ( + <> + Thank you for your feedback! + + ); + + if (variant === 'aside' && asideEndSlot) { + return ( +
+
+ {submittedBody} +
+
{asideEndSlot}
+
+ ); + } + + return ( +
+ {submittedBody} +
+ ); + } + + if (variant === 'aside') { + const buttonsRow = ( +
+ + +
+ ); + + return ( +
+

Was this helpful?

+ {buttonsRow} + {feedback === 'no' && ( +
+