diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index fb7b1386..7feb5a62 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -3,4 +3,4 @@
github: sethcottle
patreon: sethcottle
ko_fi: sethcottle
-custom: [https://buymeacoffee.com/seth, https://paypal.me/sethcottle]
+custom: ["https://buymeacoffee.com/seth", "https://paypal.me/sethcottle"]
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 4a8ecea3..612027eb 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -39,4 +39,4 @@ Please confirm that you've met the following criteria before submitting your con
---
## 🚀 Additional Notes
-
\ No newline at end of file
+
diff --git a/.github/workflows/contrast-check.yml b/.github/workflows/contrast-check.yml
index 3d7c3b2d..4406538e 100644
--- a/.github/workflows/contrast-check.yml
+++ b/.github/workflows/contrast-check.yml
@@ -13,36 +13,36 @@ jobs:
uses: actions/checkout@v3
with:
fetch-depth: 0
-
+
- name: Setup for PR comparison
run: |
echo "Fetching base branch for comparison"
git fetch origin ${{ github.base_ref }}
-
+
- name: Contrast Check (Review Me)
run: |
cat > contrast-check.sh << 'EOF'
#!/bin/bash
-
+
# WCAG Minimum Contrast Ratio
MIN_CONTRAST=4.5
-
+
FAILED=0
ALL_RESOLVED=1
NEEDS_MANUAL_REVIEW=0
-
+
# Only get buttons that were modified in the PR
echo "Finding changed button styles..."
BUTTON_CLASSES=$(git diff origin/$GITHUB_BASE_REF -- css/brands.css | grep -E "^\+.*\.button-[a-zA-Z0-9]+" | sed -E 's/.*\.button-([a-zA-Z0-9]+).*/\1/' | sort -u)
-
+
if [[ -z "$BUTTON_CLASSES" ]]; then
echo "✅ No button changes to check."
exit 0
fi
-
+
echo "Found button classes to check: $BUTTON_CLASSES"
echo "🔍 Auditing CSS for contrast issues..."
-
+
# Function to normalize hex colors to lowercase
normalize_color() {
local color="$1"
@@ -52,56 +52,56 @@ jobs:
echo ""
fi
}
-
+
# Function to calculate luminance
get_luminance() {
local color="$1"
-
+
if [[ -z "$color" || "$color" == "#" ]]; then
echo 0
return
fi
-
+
color="${color#'#'}"
-
+
if [[ ${#color} -ne 6 ]]; then
echo 0
return
fi
-
+
r=$(printf "%d" 0x${color:0:2} 2>/dev/null || echo 0)
g=$(printf "%d" 0x${color:2:2} 2>/dev/null || echo 0)
b=$(printf "%d" 0x${color:4:2} 2>/dev/null || echo 0)
-
+
r=$(awk "BEGIN { print ($r/255 <= 0.03928) ? ($r/255)/12.92 : ((($r/255) + 0.055)/1.055) ^ 2.4 }")
g=$(awk "BEGIN { print ($g/255 <= 0.03928) ? ($g/255)/12.92 : ((($g/255) + 0.055)/1.055) ^ 2.4 }")
b=$(awk "BEGIN { print ($b/255 <= 0.03928) ? ($b/255)/12.92 : ((($b/255) + 0.055)/1.055) ^ 2.4 }")
-
+
echo $(awk "BEGIN { print (0.2126 * $r) + (0.7152 * $g) + (0.0722 * $b) }")
}
-
+
# Function to calculate contrast ratio
get_contrast_ratio() {
local lum1=$(get_luminance "$1")
local lum2=$(get_luminance "$2")
-
+
if [[ -z "$lum1" || -z "$lum2" ]]; then
echo 0
return
fi
-
+
if (( $(awk "BEGIN { print ($lum1 > $lum2) ? 1 : 0 }") )); then
awk "BEGIN { printf \"%.5f\", ($lum1 + 0.05) / ($lum2 + 0.05) }"
else
awk "BEGIN { printf \"%.5f\", ($lum2 + 0.05) / ($lum1 + 0.05) }"
fi
}
-
+
# Function to extract hex color
extract_color() {
local input="$1"
local color=""
-
+
if [[ "$input" =~ "#[0-9a-fA-F]{6}" ]]; then
color=$(echo "$input" | grep -o "#[0-9a-fA-F]\{6\}")
elif [[ "$input" =~ "1px solid #" ]]; then
@@ -111,11 +111,11 @@ jobs:
elif [[ "$input" =~ "#" ]]; then
color=$(echo "$input" | grep -o "#[0-9a-fA-F]*" | head -1)
fi
-
+
# Return normalized (lowercase) hex color
normalize_color "$color"
}
-
+
# Check contrast
check_contrast() {
local text_color="$1"
@@ -126,25 +126,25 @@ jobs:
local is_background_check="$6"
local button_name="$7"
local check_failed=0
-
+
# Normalize all colors to lowercase for comparison
text_color=$(normalize_color "$text_color")
background_color=$(normalize_color "$background_color")
border_color=$(normalize_color "$border_color")
recommend_stroke=$(normalize_color "$recommend_stroke")
-
+
if [[ -z "$text_color" || -z "$background_color" ]]; then
return 0
fi
-
+
local contrast_ratio=$(get_contrast_ratio "$text_color" "$background_color")
-
+
if [[ -z "$contrast_ratio" ]]; then
contrast_ratio=0
fi
-
+
contrast_ratio=$(printf "%.2f" "$contrast_ratio")
-
+
# Case-insensitive comparison for hex colors
if (( $(awk "BEGIN { print ($contrast_ratio < $MIN_CONTRAST) ? 1 : 0 }") )); then
if [[ -n "$border_color" && "$border_color" == "$recommend_stroke" && "$is_background_check" -eq 1 ]]; then
@@ -159,70 +159,70 @@ jobs:
echo "✅ [$context → $button_name] Contrast ratio $contrast_ratio passes WCAG"
check_failed=0
fi
-
+
return $check_failed
}
-
+
# For each button class, check its properties
for button_class in $BUTTON_CLASSES; do
echo "Checking button: $button_class"
-
+
# Extract button section
# Avoid partial matches
button_start=$(grep -n "\.button-$button_class\( \|{\)" css/brands.css | cut -d: -f1)
if [[ -z "$button_start" ]]; then
button_start=$(grep -n "\.button-$button_class$" css/brands.css | cut -d: -f1)
fi
-
+
if [[ -z "$button_start" ]]; then
echo "Could not find button-$button_class in css/brands.css"
continue
fi
-
+
# Look for the next closing bracket
button_end=$(tail -n +$button_start css/brands.css | grep -n "}" | head -1 | cut -d: -f1)
if [[ -z "$button_end" ]]; then
button_end=10
fi
-
+
button_section=$(tail -n +$button_start css/brands.css | head -n $button_end)
-
+
# Check for gradient
if echo "$button_section" | grep -q "background-image"; then
echo "🚩 [./css/brands.css → $button_class] Detected gradient background → Flagging for manual review."
NEEDS_MANUAL_REVIEW=1
continue
fi
-
+
# Extract colors
text_color=$(echo "$button_section" | grep "button-text" | grep -o "#[0-9A-Fa-f]*")
bg_color=$(echo "$button_section" | grep "button-background" | grep -o "#[0-9A-Fa-f]*")
border_color=$(extract_color "$(echo "$button_section" | grep "button-border")")
-
+
button_failed=0
-
+
# Check text vs background
if [[ -n "$text_color" && -n "$bg_color" ]]; then
check_contrast "$text_color" "$bg_color" "TEXT vs BUTTON" "$border_color" "" 0 "$button_class"
button_failed=$((button_failed | $?))
fi
-
+
# Check button vs light theme
if [[ -n "$bg_color" ]]; then
check_contrast "#ffffff" "$bg_color" "BUTTON vs LIGHT THEME" "$border_color" "#000000" 1 "$button_class"
button_failed=$((button_failed | $?))
-
+
# Check button vs dark theme
check_contrast "#121212" "$bg_color" "BUTTON vs DARK THEME" "$border_color" "#ffffff" 1 "$button_class"
button_failed=$((button_failed | $?))
fi
-
+
if [[ $button_failed -eq 1 ]]; then
FAILED=1
ALL_RESOLVED=0
fi
done
-
+
# Final report
if [[ "$NEEDS_MANUAL_REVIEW" -eq 1 ]]; then
echo "⚠️ Manual review required for gradients!"
@@ -235,7 +235,7 @@ jobs:
exit 1
fi
EOF
-
+
chmod +x contrast-check.sh
./contrast-check.sh
env:
diff --git a/.gitignore b/.gitignore
index a036416d..69154689 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,3 @@
.DS_Store
.idea
-.devcontainer
\ No newline at end of file
+.devcontainer
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..e7f6014e
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,20 @@
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v5.0.0
+ hooks:
+ - id: trailing-whitespace
+ args: ["--markdown-linebreak-ext=md,markdown"]
+ exclude: \.svg$
+ - id: end-of-file-fixer
+ exclude: \.svg$
+ - id: check-yaml
+ - id: double-quote-string-fixer
+ exclude: \.svg$
+ - repo: https://github.com/gitleaks/gitleaks
+ rev: v8.26.0
+ hooks:
+ - id: gitleaks
+ - repo: https://github.com/thoughtworks/talisman
+ rev: v1.37.0
+ hooks:
+ - id: talisman-commit
diff --git a/README.md b/README.md
index 7fa6ebd7..e854864f 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@

# LittleLink
-The DIY self-hosted LinkTree alternative. LittleLink has more than 100 branded button styles you can easily use, with more regularly added by our community in this repo and in [LittleLink Extended](https://github.com/sethcottle/littlelink-extended).
+The DIY self-hosted LinkTree alternative. LittleLink has more than 100 branded button styles you can easily use, with more regularly added by our community in this repo and in [LittleLink Extended](https://github.com/sethcottle/littlelink-extended).
---
### 🆕 LittleLink Button Builder
diff --git a/VERSION.md b/VERSION.md
index fd786924..d6e6ed77 100644
--- a/VERSION.md
+++ b/VERSION.md
@@ -16,7 +16,7 @@
### v3.4.0 - 3/04/2025
- Added Matrix
-
+
### v3.3.0 - 03/01/2025
- Updated Facebook Logo
- Updated Messenger Logo
@@ -28,7 +28,7 @@
### v3.1.1 - 1/28/2025
- Fixed the alt text for Obsidian (`PR #138` / `@timtjtim`)
-
+
### v3.1.0 - 1/20/2025
- Added alternate YouTube button (`PR #138` / `@Omikorin`)
- Fixed `index.html` accessibilty issues (`PR #137` / `@rosahaj`)
diff --git a/css/brands.css b/css/brands.css
index 9fb18865..a5c2541e 100644
--- a/css/brands.css
+++ b/css/brands.css
@@ -630,4 +630,4 @@
--button-text:#ffffff;
--button-background:#0B5CFF;
--button-border:1px solid #FFFFFF;
-}
\ No newline at end of file
+}
diff --git a/css/reset.css b/css/reset.css
index d158daf5..067b17eb 100644
--- a/css/reset.css
+++ b/css/reset.css
@@ -88,4 +88,4 @@ select {
/* Remove touch callout on iOS */
a {
-webkit-touch-callout: none;
-}
\ No newline at end of file
+}
diff --git a/css/style.css b/css/style.css
index afb4ce46..ab74092f 100644
--- a/css/style.css
+++ b/css/style.css
@@ -100,7 +100,7 @@
--scale-3:1.953rem; /* 31px */
--scale-4:2.441rem; /* 39px */
--scale-5:3.052rem; /* 49px */
-
+
/* Spacing units */
--spacing-xs:0.5rem; /* 8px */
--spacing-s:1rem; /* 16px */
@@ -237,7 +237,7 @@ a:hover {
.avatar--rounded {
border-radius: 50%;
}
-
+
/* Modifier for slightly rounded corners */
.avatar--soft {
border-radius: 0.5rem; /* 8px rounded corners */
@@ -246,7 +246,7 @@ a:hover {
/* Theme System
–––––––––––––––––––––––––––––––––––––––––––––––––– */
/* Light theme is default above */
-
+
/* Dark theme */
:root.theme-dark {
color-scheme:dark;
diff --git a/docker/README.md b/docker/README.md
index e4cf075e..9e0c6494 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -228,4 +228,3 @@ docker run -d --name site1 -p 8080:80 littlelink-site1
# Run second site on port 8081
docker run -d --name site2 -p 8081:80 littlelink-site2
```
-
diff --git a/index.html b/index.html
index abbf3980..b1863923 100644
--- a/index.html
+++ b/index.html
@@ -1,6 +1,6 @@
-
LittleLink
-
+
-
+
-
+
-
+
-
+
-
+
-
@@ -46,7 +46,7 @@
-
-
Listen on Amazon Music
-
+
Listen on Amazon Music
+
Apple App Store
-
+
Apple Invites
-
Listen on Apple Music
-
+
Listen on Apple Music
+
Listen on Apple Music
Listen on Apple Podcasts
-
+
Listen on Apple Podcasts
@@ -99,7 +99,7 @@
Behance
-
+
Bluesky
@@ -183,7 +183,7 @@
Kick
-
+
Kickstarter
@@ -225,10 +225,10 @@
Get it from Microsoft
-
+
Notion
-
+
Obsidian
@@ -384,7 +384,7 @@
Visit Our Shop
-
+
10% Discount
@@ -398,12 +398,12 @@
LittleLink Extended
-
+
-
+
diff --git a/privacy.html b/privacy.html
index b2c61563..d1d3aee6 100644
--- a/privacy.html
+++ b/privacy.html
@@ -1,6 +1,6 @@
-
-
+
@@ -28,51 +28,51 @@
-
+
← Back to main page
-
+
Privacy Overview
-
+
Analytics
The services contained in this section enable the Owner to monitor and analyze web traffic and can be used to keep track of User behavior.
-
+
Example LLC
Personal Data: various types of Data as specified in the privacy policy of the service
Privacy Policy
-
+
External Content
This type of service allows you to view content hosted on external platforms directly from the pages of this website and interact with them.
This type of service might still collect web traffic data for the pages where the service is installed, even when Users do not use it.
-
+
Example LLC
Personal Data: Usage Data; various types of Data as specified in the privacy policy of the service
Privacy Policy
-
+
Hosting and Infrastructure
This type of service has the purpose of hosting Data and files that enable this website to exist.
Some services among those listed below, if any, may work through geographically distributed servers, making it difficult to determine the actual location where the Personal Data are stored.
-
+
Example LLC
Personal Data: various types of Data as specified in the privacy policy of the service
Privacy Policy
-
+
@@ -81,4 +81,4 @@
-
\ No newline at end of file
+