Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ This file is automatically synced from docs/pages/contribute/contributing.mdx du
⚠️ DO NOT EDIT THIS FILE DIRECTLY.
Instead, make changes to docs/pages/contribute/contributing.mdx and make this be synced just by running the build command.

Both files contain the same content: the docs file is there to render the content on the website,
while this copy is needed to make it highlighted in the repository dashboard.
Both files contain the same content: the docs file is there to render the content on the website, while this copy is needed to make it highlighted in the repository dashboard.
-->

# **Contributing guidelines**
Expand Down Expand Up @@ -279,8 +278,8 @@ the site renders each one as its own section on the page: "Authored by", "Review
the bar is the same one: the page has to be more accurate afterwards than it was before.

The three roles carry the same weight. A page is only as good as the writing, the checking, and the sign-off behind
it, and the Security Frameworks need all three to be worth trusting. What follows is about describing different kinds of work
accurately, not about ranking contributors.
it, and the Security Frameworks need all three to be worth trusting. What follows is about describing different kinds
of work accurately, not about ranking contributors.

The short version: a reviewer answers "is this page good?", a fact-checker answers "is this statement true?". Both
roles are earned by the work of checking, not by the size of the diff. Text that adds words without settling either
Expand Down Expand Up @@ -482,6 +481,9 @@ Pages with minimal content which need more work to cover the topic need to inclu

This page is also open for contributions. Suggest improvements to our style and guidelines in the GitHub repo.

Funding the work is another way to contribute. [Support the Frameworks](https://securityalliance.org/donate) to help
keep them free, open, and independent.

## About this page

Originally inspired by the [Ethereum Protocol Fellows](https://github.com/eth-protocol-fellows/protocol-studies).
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ Every contribution helps strengthen the resource and improve security practices
See our [Contributing Guide](https://github.com/security-alliance/frameworks/blob/develop/CONTRIBUTING.md) for details
on how to get started.

Funding is another way to contribute. [Support the Frameworks](https://securityalliance.org/donate) to help keep
this resource free, open, and independent.

## License

Security Frameworks Β© 2026 by Security Alliance, licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/).
46 changes: 46 additions & 0 deletions components/donate/DonateNavButton.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
.donate-nav-button {
display: inline-flex;
align-items: center;
align-self: stretch; /* matches the search box height */
gap: 0.5rem;
flex-shrink: 0;
padding: 0 0.75rem;
font-size: 0.875rem;
line-height: 1.25rem;
font-weight: 450;
white-space: nowrap;
text-decoration: none;
color: var(--vocs-text-color-secondary);
background-color: var(--vocs-background-color-surface);
border: 1px solid var(--vocs-border-color-primary);
border-radius: 0.75rem;
transition: color 0.1s ease, background-color 0.1s ease, border-color 0.1s ease;
}

/* Light mode borders are set alongside the search trigger in docs/pages/_root.css,
which overrides the Vocs default for both controls together. */

.donate-nav-button:hover {
color: var(--vocs-text-color-primary);
background-color: var(--vocs-background-color-surfaceTint);
}

/* The one accent: SEAL blue, matching the sidebar heart and the footer buttons.
Deliberately not `--vocs-color-accent`, which defaults to
light-dark(black, white) and is not overridden by this site. */
.donate-nav-button svg {
color: #4339db;
flex-shrink: 0;
}

:root[data-vocs-theme='dark'] .donate-nav-button svg {
color: #6b63e8; /* Brighter SEAL Blue for dark themes */
}

/* Follows the search input, which Vocs hides below its `md` breakpoint.
Vocs overrides that breakpoint to 748px, so this is not Tailwind's default. */
@media (width < 748px) {
.donate-nav-button {
display: none;
}
}
47 changes: 47 additions & 0 deletions components/donate/DonateNavButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"use client";

import { createPortal } from 'react-dom'
import { DONATE_URL } from '../shared/donate'
import { usePortalTargets } from './usePortalTargets'
import './DonateNavButton.css'

const LABEL = 'Support us'
const TITLE = 'Help sustain the Frameworks'

function DonateButton() {
return (
<a
className="donate-nav-button"
href={DONATE_URL}
rel="noopener noreferrer"
target="_blank"
title={TITLE}
>
<svg
aria-hidden="true"
fill="currentColor"
focusable="false"
height="16"
viewBox="0 0 24 24"
width="16"
>
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
</svg>
{LABEL}
</a>
)
}

/**
* Renders a donation button beside the top nav search box.
*
* Vocs exposes no slot in the top nav, so the button is portalled into
* `[data-v-gutter-top-left]`, the flex row that holds the logo and search.
* It is hidden on small screens along with the search input itself, where the
* mobile nav drawer carries the donation link instead.
*/
export function DonateNavButton() {
const targets = usePortalTargets('[data-v-gutter-top-left]')

return targets.map(target => createPortal(<DonateButton />, target, 'donate-nav-button'))
}
29 changes: 29 additions & 0 deletions components/donate/DonateSidebarLink.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.donate-sidebar-divider {
width: 1px;
height: 1rem;
margin: 0 0.25rem;
background-color: var(--vocs-background-color-primary);
}

.donate-sidebar-link {
display: flex;
align-items: center;
justify-content: center;
width: 1.75rem;
height: 1.75rem;
color: #4339db; /* SEAL Blue */
transition: color 0.15s ease, transform 0.15s ease;
}

.donate-sidebar-link:hover {
color: #352eb0; /* Darker SEAL Blue */
transform: scale(1.1);
}

:root[data-vocs-theme='dark'] .donate-sidebar-link {
color: #6b63e8; /* Brighter SEAL Blue for dark themes */
}

:root[data-vocs-theme='dark'] .donate-sidebar-link:hover {
color: #8c85f0;
}
48 changes: 48 additions & 0 deletions components/donate/DonateSidebarLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"use client";

import { createPortal } from 'react-dom'
import { DONATE_URL } from '../shared/donate'
import { usePortalTargets } from './usePortalTargets'
import './DonateSidebarLink.css'

const LABEL = 'Help sustain the Frameworks'

function DonateAnchor() {
return (
<>
<div className="donate-sidebar-divider" />
<a
aria-label={LABEL}
className="donate-sidebar-link"
href={DONATE_URL}
rel="noopener noreferrer"
target="_blank"
title={LABEL}
>
<svg
aria-hidden="true"
fill="currentColor"
focusable="false"
height="18"
viewBox="0 0 24 24"
width="18"
>
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
</svg>
</a>
</>
)
}

/**
* Renders a donation link alongside the social icons.
*
* Vocs only accepts a fixed set of `socials` icons, so the link is portalled
* into every `[data-v-socials]` row instead. That covers both the desktop
* sidebar footer and the mobile nav drawer, which mounts on demand.
*/
export function DonateSidebarLink() {
const targets = usePortalTargets('[data-v-socials]')

return targets.map(target => createPortal(<DonateAnchor />, target, 'donate-sidebar-link'))
}
33 changes: 33 additions & 0 deletions components/donate/usePortalTargets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"use client";

import { useEffect, useState } from 'react'

/**
* Tracks every element matching `selector`, so content can be portalled into
* parts of the Vocs chrome that expose no slot of their own.
*
* Targets are re-read on DOM mutations, which is what makes this work for
* regions that mount on demand, such as the mobile nav drawer.
*/
export function usePortalTargets(selector: string): Element[] {
const [targets, setTargets] = useState<Element[]>([])

useEffect(() => {
const sync = () => {
const found = Array.from(document.querySelectorAll(selector))
// Only commit a new array when the matched set actually changed, so the
// observer does not loop on the nodes the caller itself portals in.
setTargets(prev =>
prev.length === found.length && prev.every((el, i) => el === found[i]) ? prev : found,
)
}

sync()

const observer = new MutationObserver(sync)
observer.observe(document.body, { childList: true, subtree: true })
return () => observer.disconnect()
}, [selector])

return targets
}
40 changes: 17 additions & 23 deletions components/footer/ContributeFooter.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@
color: #352eb0; /* Darker SEAL Blue */
}

.contribute-footer-actions {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
justify-content: center;
align-items: center;
}

.contribute-footer-note {
font-size: 0.9rem;
color: #6C757D; /* Gray from SEAL palette */
margin-top: 0.6rem;
line-height: 1.3;
}

.contribute-footer-button {
display: inline-block;
font-weight: 500;
Expand All @@ -70,31 +85,10 @@
border: none;
}

:root[data-vocs-theme='dark'] .contribute-footer-title {
color: #6b63e8; /* Brighter SEAL Blue for dark themes */
}

:root[data-vocs-theme='dark'] .contribute-footer-subtitle {
color: #a8adb2; /* Lighter gray for dark themes */
}

:root[data-vocs-theme='dark'] .contribute-footer-link {
color: #6b63e8; /* Brighter SEAL Blue for dark themes */
}

:root[data-vocs-theme='dark'] .contribute-footer-link:hover {
color: #8c85f0; /* Even brighter SEAL Blue for dark themes */
}

:root[data-vocs-theme='dark'] .contribute-footer-button {
color: #FFFFFF;
border-color: #6b63e8; /* Brighter SEAL Blue for dark themes */
background-color: #6b63e8;
}

:root[data-vocs-theme='dark'] .contribute-footer-button:hover {
background-color: #8c85f0; /* Even brighter SEAL Blue for dark themes */
border-color: #8c85f0;
color: #1a1a1a; /* Darker background for contrast */
box-shadow: 0 4px 8px rgba(107, 99, 232, 0.3);
:root[data-vocs-theme='dark'] .contribute-footer-note {
color: #a8adb2; /* Lighter gray for dark themes */
}
38 changes: 27 additions & 11 deletions components/footer/ContributeFooter.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
"use client";

import { useEffect, useState } from 'react';
import { DONATE_URL } from '../shared/donate'
import './ContributeFooter.css'

interface ContributeFooterProps {
learnMoreUrl?: string;
contributeUrl?: string;
donateUrl?: string;
}

export function ContributeFooter({
learnMoreUrl = "/contribute/contributing",
contributeUrl = "https://github.com/security-alliance/frameworks/blob/develop/docs/pages/"
export function ContributeFooter({
learnMoreUrl = "/contribute/contributing",
contributeUrl = "https://github.com/security-alliance/frameworks/blob/develop/docs/pages/",
donateUrl = DONATE_URL
}: ContributeFooterProps) {
const [currentPath, setCurrentPath] = useState<string>("");

Expand All @@ -34,14 +37,27 @@ export function ContributeFooter({
Learn more
</a>
</p>
<a
href={`${contributeUrl}${currentPath}`}
className="contribute-footer-button"
target="_blank"
rel="noopener noreferrer"
>
✏️ Contribute today!
</a>
<div className="contribute-footer-actions">
<a
href={`${contributeUrl}${currentPath}`}
className="contribute-footer-button"
target="_blank"
rel="noopener noreferrer"
>
✏️ Contribute today!
</a>
<a
href={donateUrl}
className="contribute-footer-button"
target="_blank"
rel="noopener noreferrer"
>
πŸ’œ Keep this work funded
</a>
</div>
<p className="contribute-footer-note">
Short on time? Supporting SEAL keeps the Frameworks free and independent.
</p>
</div>
</div>
);
Expand Down
3 changes: 3 additions & 0 deletions components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ export { TagFilter } from './tags/TagFilter'
export { TagList } from './tags/TagList'
export { AttributionList } from './attribution/AttributionList'
export { ContributeFooter } from './footer/ContributeFooter'
export { DonateSidebarLink } from './donate/DonateSidebarLink'
export { DonateNavButton } from './donate/DonateNavButton'
export { Contributors } from './contributors/Contributors'
export { BenchmarkList } from './benchmark/Benchmark'
export { CertList } from './cert/CertList'
export { ExportAllCerts } from './cert/ExportAllCerts'
export type { Control, Section, CertListProps, ControlState, ControlData } from './cert/types'
export * from './shared/tagColors'
export * from './shared/donate'
export { CertifiedProtocols } from './cert/certified-protocols/CertifiedProtocols'
export { CertifiedProtocolsWrapper } from './cert/certified-protocols/CertifiedProtocolsWrapper'
export { BadgeLegend } from './contributors/BadgeLegend'
Expand Down
5 changes: 5 additions & 0 deletions components/shared/donate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* Donation link
*/

export const DONATE_URL = 'https://securityalliance.org/donate'
Loading
Loading