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
new file mode 100644
index 00000000..612027eb
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,42 @@
+## π Description
+
+
+---
+
+## β
Contribution Checklist
+Please confirm that you've met the following criteria before submitting your contribution:
+
+- [ ] **Widespread Recognition:** The brand has widespread recognition and is suitable for the core LittleLink repository (most additions belong in [LittleLink Extended](https://github.com/sethcottle/littlelink-extended)).
+- [ ] **Brand Styling Guidelines:** Youβve followed the brand's official styling guidelines (if available).
+- [ ] **Consistent Styling:** If no guidelines exist, the button style is consistent with the brand's public image (e.g., website, social media).
+- [ ] **Icon Clarity:** The brand's logo/icon is clear and recognizable in a 24x24px format.
+ - [ ] If the primary logo doesnβt scale well, youβve adapted it using the brandβs social media avatar or favicon while maintaining the essence of the original logo.
+ - [ ] The icon is provided in `.svg` format.
+- [ ] **Theme Testing:** You've tested the button against both light and dark themes:
+ - [ ] Manually swapped `theme-auto.css` with `theme-light.css` and `theme-dark.css` in `index.html` to check contrast or used [LittleLink Button Builder](https://builder.littlelink.io) contrast checker.
+ - [ ] Added a `#000000`/`#FFFFFF` stroke if necessary to improve contrast and accessibility. [LittleLink Button Builder](https://builder.littlelink.io) will automatically recommend when to add a stroke.
+- [ ] **Accessibility Compliance:** The button's background and text colors meet visual accessibility standards (unless it contradicts brand guidelines).
+- [ ] **Alphabetical Order:** Your contribution is alphabetically organized in `brands.css` and `index.html`.
+- [ ] **Button Preview:** You've added a button preview in `index.html`.
+- [ ] **Variant Naming Schema:** If adding a variant button (e.g., inverted color scheme):
+ - [ ] Naming follows the existing pattern (`[Brand Name] Alt` and `.button-brandname-alt`).
+ - [ ] Any additional icons are named according to `brandname-alt.svg` schema.
+- [ ] **Proper Capitalization:**
+ - [ ] In `brands.css`, the brand name comment follows `/* Brand Name */` format.
+ - [ ] Code uses lowercase for `.button.button-brandname`.
+ - [ ] In `index.html`, comments reflect `` format.
+ - [ ] Button text and `alt` attributes match the brandβs official capitalization.
+- [ ] **PR Details:**
+ - [ ] Included a brief description of the brand addition.
+ - [ ] Included a screenshot of the new button(s).
+ - [ ] Provided relevant information on the brandβs global/regional reach or usage stats.
+
+---
+
+## πΈ Screenshot
+
+
+---
+
+## π Additional Notes
+
diff --git a/.github/workflows/contrast-check.yml b/.github/workflows/contrast-check.yml
new file mode 100644
index 00000000..4406538e
--- /dev/null
+++ b/.github/workflows/contrast-check.yml
@@ -0,0 +1,242 @@
+name: "Contrast Check"
+on:
+ pull_request:
+ paths:
+ - 'css/brands.css'
+ workflow_dispatch: # Manual trigger for testing
+
+jobs:
+ contrast-check:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ 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"
+ if [[ -n "$color" ]]; then
+ echo "$color" | tr '[:upper:]' '[:lower:]'
+ else
+ 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
+ color=$(echo "$input" | sed -E 's/.*1px solid (#[0-9a-fA-F]{6}).*/\1/')
+ elif [[ "$input" =~ "solid #" ]]; then
+ color=$(echo "$input" | sed -E 's/.*solid (#[0-9a-fA-F]{6}).*/\1/')
+ 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"
+ local background_color="$2"
+ local context="$3"
+ local border_color="$4"
+ local recommend_stroke="$5"
+ 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
+ echo "β
[$context β $button_name] Contrast ratio $contrast_ratio fails WCAG but has a $recommend_stroke border β Treated as passing."
+ echo "β
[$context β $button_name] Issue resolved by stroke β Fully passing."
+ check_failed=0
+ else
+ echo "β [$context β $button_name] Contrast ratio $contrast_ratio fails WCAG β Recommend adding a $recommend_stroke stroke."
+ check_failed=1
+ fi
+ else
+ 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!"
+ exit 1
+ elif [[ "$ALL_RESOLVED" -eq 1 ]]; then
+ echo "β
All contrast checks passed!"
+ exit 0
+ else
+ echo "β Contrast issues found!"
+ exit 1
+ fi
+ EOF
+
+ chmod +x contrast-check.sh
+ ./contrast-check.sh
+ env:
+ GITHUB_BASE_REF: ${{ github.base_ref }}
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 69521469..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
@@ -65,12 +65,14 @@ Duplicate the [LittleLink Template on Figma Community](https://www.figma.com/com
### π Supporters
You can support LittleLink by [buying me a coffee](https://www.buymeacoffee.com/seth). You can also have your name or your company added to this section and the supporters page of the [LittleLink.io](https://littlelink.io) website.
-#### π’ Business Supporters ($75 tier)
+#### π’ Business Supporters
+β’ **[Unallocated Space](https://connect.unallocatedspace.org/)** | Hacker/Makerspace in Millersville, MD, serving the greater Baltimore-Washington area
+
β’ **[links.dev](https://github.com/fatih-yavuz/links.dev)**
[](https://www.buymeacoffee.com/seth/e/50574)
-#### β¨ Individual Supporters ($25 tier)
+#### β¨ Individual Supporters
β’ **[Drew Davis](https://connect.davisdre.me)**
β’ **[Robotter112](https://robotter112.de/)**
@@ -78,17 +80,10 @@ You can support LittleLink by [buying me a coffee](https://www.buymeacoffee.com/
[](https://www.buymeacoffee.com/seth/e/50573)
#### π Active GitHub Sponsors
-β’ **[@nghialele](https://github.com/nghialele)**
-
β’ **[Your Name Here](https://github.com/sponsors/sethcottle)**
[](https://github.com/sponsors/sethcottle)
-#### β€οΈ Patreon Members
-β’ **[Your Name Here](https://www.patreon.com/sethcottle)**
-
-[](https://www.patreon.com/sethcottle)
-
#### π₯° More Ways to Support LittleLink
[](https://www.buymeacoffee.com/seth/)
@@ -116,3 +111,19 @@ To help build a more privacy focused product, we recommend using [Fathom Analyti
###### ** Analytics in this dashboard start May 03, 2022. View this [Google Sheets file](https://docs.google.com/spreadsheets/d/1GL4SroAdH-OZphBVR5z-BoSukHIEVJfao25q_e9-Ii8/edit?usp=sharing) with the generic unique pageview data from Google Analytics.
[](https://usefathom.com/ref/EQVZMV)
+
+---
+### π³ Docker Support
+LittleLink includes Docker support for easy deployment and development. All Docker-related files are located in the `docker/` directory.
+
+To run LittleLink using Docker:
+
+```bash
+docker compose -f docker/compose.yaml up
+```
+
+This will make the site available at http://localhost:8080
+
+For more information about Docker configuration, see [docker/README.md](docker/README.md).
+
+---
diff --git a/VERSION.md b/VERSION.md
index 359170e2..d6e6ed77 100644
--- a/VERSION.md
+++ b/VERSION.md
@@ -1,10 +1,22 @@
# LittleLink Version History
-## Current Version: v3.4.0
+## Current Version: v3.7.0
+
+### v3.7.0 - 4/18/2025
+- Added [Meetup](https://www.meetup.com/).
+
+### v3.6.0 - 3/25/2025
+- Finally adds Docker support to LittleLink. See [PR #151](https://github.com/sethcottle/littlelink/pull/151), thank you [@lllahaye](https://github.com/lllahaye).
+ - Docker support has been a long-standing community request. While I previously closed similar PRs in an effort to keep the LittleLink repo as minimal as possible, several community forks emerged that added Docker support independently. Over the last few weeks I had been reconsidering this stanceβthis PR arrived at just the right time.
+- Updated the brand color for Signal.
+
+### v3.5.0 - 3/10/2025
+- Added LittleLink Extended information in `index.html`
+- Added `PULL_REQUEST_TEMPLATE.md` to `.github` which is a reflection of [submitting a new brand](https://github.com/sethcottle/littlelink/wiki/Submitting-a-new-brand-to-LittleLink) wiki.
### v3.4.0 - 3/04/2025
- Added Matrix
-
+
### v3.3.0 - 03/01/2025
- Updated Facebook Logo
- Updated Messenger Logo
@@ -16,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 cec018c4..a5c2541e 100644
--- a/css/brands.css
+++ b/css/brands.css
@@ -352,6 +352,19 @@
--button-border:1px solid #000000;
}
+/* Meetup */
+.button-meetup {
+ --button-text:#000000;
+ --button-background:#ffffff;
+ --button-border:1px solid #000000;
+}
+
+/* Meetup Alt */
+.button-meetup-alt {
+ --button-text:#ffffff;
+ --button-background:#ED1C40;
+}
+
/* Medium */
.button-medium {
--button-text:#ffffff;
@@ -429,7 +442,14 @@
/* Signal */
.button-signal {
--button-text:#ffffff;
- --button-background:#3a76f0;
+ --button-background:#3B45FD;
+}
+
+/* Signal Alt */
+.button-signal-alt {
+ --button-text:#3B45FD;
+ --button-background:#E3E8FE;
+ --button-border:1px solid #000000;
}
/* Slack */
@@ -610,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/.dockerignore b/docker/.dockerignore
new file mode 100644
index 00000000..f195f63b
--- /dev/null
+++ b/docker/.dockerignore
@@ -0,0 +1,46 @@
+# Include any files or directories that you don't want to be copied to your
+# container here (e.g., local build artifacts, temporary files, etc.).
+#
+# For more help, visit the .dockerignore file reference guide at
+# https://docs.docker.com/go/build-context-dockerignore/
+
+# Git
+.git
+.github
+.gitignore
+
+# Docker
+.dockerignore
+docker-compose*
+compose.yaml
+
+# Logs
+*.log
+
+# Editor directories and files
+.idea
+.vscode
+**/.DS_Store
+
+# Documentation files
+README*
+LICENSE*
+VERSION*
+CONTRIBUTING*
+
+# Exclude unnecessary files
+**/__pycache__
+**/.venv
+**/bin
+**/obj
+**/charts
+**/.env
+**/secrets.dev.yaml
+**/values.dev.yaml
+
+# Keep only essential files for the static website
+!index.html
+!privacy.html
+!css/
+!images/
+!fonts/
diff --git a/docker/Dockerfile b/docker/Dockerfile
new file mode 100644
index 00000000..0c03eb53
--- /dev/null
+++ b/docker/Dockerfile
@@ -0,0 +1,35 @@
+FROM nginx:alpine
+
+# Copy static website files
+COPY . /usr/share/nginx/html/
+
+# Configure nginx with basic optimization and logging to stdout/stderr
+RUN echo 'server { \
+ listen 80; \
+ server_name localhost; \
+ root /usr/share/nginx/html; \
+ index index.html index.htm; \
+ \
+ # Enable access logging to stdout \
+ access_log /dev/stdout; \
+ error_log /dev/stderr; \
+ \
+ # Enable gzip compression \
+ gzip on; \
+ gzip_vary on; \
+ gzip_types text/plain text/css application/json application/javascript; \
+ \
+ # Basic cache settings \
+ location ~* \\.(?:css|js|jpg|jpeg|gif|png|ico|svg)$ { \
+ expires 7d; \
+ add_header Cache-Control "public"; \
+ } \
+}' > /etc/nginx/conf.d/default.conf
+
+# Forward nginx logs to Docker log collector
+RUN ln -sf /dev/stdout /var/log/nginx/access.log && \
+ ln -sf /dev/stderr /var/log/nginx/error.log
+
+EXPOSE 80
+
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/docker/README.md b/docker/README.md
new file mode 100644
index 00000000..9e0c6494
--- /dev/null
+++ b/docker/README.md
@@ -0,0 +1,230 @@
+# Docker for LittleLink
+
+Docker configuration to run LittleLink in a container.
+
+## File Structure
+
+- `Dockerfile`: Defines how the image is built using nginx:alpine
+- `compose.yaml`: Configuration for Docker Compose with volumes for development
+- `.dockerignore`: Excludes unnecessary files from the image
+
+## Technical Details
+
+### Base Image
+- Uses `nginx:alpine` for minimal image size (~20MB)
+- Includes gzip compression for static files
+- Optimized cache configuration for CSS, JavaScript, and images
+- Configured to forward nginx logs to Docker log collector
+
+### Volumes and Ports
+- Mounts the project root directory as a volume for live development
+- Exposes port 80 in the container, mapped to 8080 on the host
+
+## Common Use Cases
+
+### Creating Personal Link Pages for Different People
+
+One of the main advantages of this Docker setup is how easily you can create multiple personalized instances of LittleLink:
+
+```bash
+# Clone the repository
+git clone https://github.com/sethcottle/littlelink.git littlelink-john
+
+# Customize the content for John
+cd littlelink-john
+# Edit index.html with John's links, customize images, etc.
+
+# Build a Docker image for John's page
+docker build -f docker/Dockerfile -t littlelink-john .
+
+# Run John's page on port 8080
+docker run -d --name john-links -p 8080:80 littlelink-john
+```
+
+For additional pages:
+
+```bash
+# Similarly for another person
+git clone https://github.com/sethcottle/littlelink.git littlelink-jane
+cd littlelink-jane
+# Customize for Jane...
+
+# Build and run on a different port
+docker build -f docker/Dockerfile -t littlelink-jane .
+docker run -d --name jane-links -p 8081:80 littlelink-jane
+```
+
+This approach allows you to:
+- Maintain separate customized sites for different people
+- Run multiple instances on different ports
+- Update each site independently
+- Easily deploy to various environments
+
+## Development vs. Production
+
+There are two main ways to use Docker with LittleLink:
+
+### Development Workflow
+
+In development, we use Docker Compose with mounted volumes to allow for live editing:
+
+```bash
+# Start development environment
+docker compose -f docker/compose.yaml up
+```
+
+This configuration:
+- Mounts local files as a volume, so changes are reflected immediately
+- Requires manual browser refresh to see changes
+- Is ideal for testing and development
+
+### Production Workflow
+
+For production, you have two options:
+
+#### Option 1: Production with Docker Compose
+
+Create a production-specific docker-compose file:
+
+```yaml
+# docker/compose.prod.yaml
+
+services:
+ web:
+ image: yourname/littlelink:latest
+ restart: always
+ ports:
+ - "8080:80"
+ # Optional volume for customizable content
+ volumes:
+ - /path/on/server/custom-content:/usr/share/nginx/html
+```
+
+Deploy using:
+
+```bash
+# Build and tag the image
+docker build -f docker/Dockerfile -t yourname/littlelink:latest .
+
+# Run in production with compose
+docker compose -f docker/compose.prod.yaml up -d
+```
+
+#### Option 2: Production with Docker Run
+
+```bash
+# Build a production image
+docker build -f docker/Dockerfile -t yourname/littlelink:latest .
+
+# Run in production (no volumes mounted)
+docker run -d --name littlelink -p 80:80 --restart always yourname/littlelink:latest
+```
+
+## Using Volumes in Production
+
+You can customize the content in production by mounting a local directory:
+
+```bash
+# Prepare a directory with your custom content
+mkdir -p /path/on/server/custom-content
+cp -r index.html css/ images/ /path/on/server/custom-content/
+
+# Run with the custom content mounted
+docker run -d --name littlelink -p 80:80 \
+ -v /path/on/server/custom-content:/usr/share/nginx/html \
+ yourname/littlelink:latest
+```
+
+With Docker Compose:
+
+```yaml
+services:
+ web:
+ image: yourname/littlelink:latest
+ ports:
+ - "80:80"
+ volumes:
+ - /path/on/server/custom-content:/usr/share/nginx/html
+```
+
+This approach:
+- Allows content customization without rebuilding the image
+- Makes it easy to update content independently of the container
+
+## Docker Commands Reference
+
+### Development Commands
+
+```bash
+# Start in development mode
+docker compose -f docker/compose.yaml up
+
+# Start in background
+docker compose -f docker/compose.yaml up -d
+
+# Stop container
+docker compose -f docker/compose.yaml down
+
+# View logs (including HTTP request logs)
+docker compose -f docker/compose.yaml logs -f
+```
+
+### Production Commands
+
+```bash
+# Build production image
+docker build -f docker/Dockerfile -t yourname/littlelink:latest .
+
+# Run production container
+docker run -d --name littlelink -p 80:80 yourname/littlelink:latest
+
+# View logs for the running container
+docker logs -f littlelink
+```
+
+## Customization
+
+### Change Port
+Edit `docker/compose.yaml` for development:
+```yaml
+ports:
+ - "8081:80" # Change 8080 to desired port
+```
+
+Or specify port when running production container:
+```bash
+docker run -p 8081:80 yourname/littlelink:latest
+```
+
+### Additional nginx Configuration
+To modify the nginx configuration, you can edit the `Dockerfile` and add your own configuration:
+
+```dockerfile
+# Example: add custom configuration
+COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
+```
+
+## Deploying to Production
+
+### Docker on VPS
+```bash
+# Pull image
+docker pull yourname/littlelink:latest
+
+# Run container
+docker run -d --name littlelink -p 80:80 yourname/littlelink:latest
+
+# With restart policy for auto-recovery
+docker run -d --name littlelink --restart unless-stopped -p 80:80 yourname/littlelink:latest
+```
+
+### Multiple Sites on One Server
+You can run multiple LittleLink instances on the same server:
+
+```bash
+# Run first site on port 8080
+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/docker/compose.yaml b/docker/compose.yaml
new file mode 100644
index 00000000..b48be3f8
--- /dev/null
+++ b/docker/compose.yaml
@@ -0,0 +1,10 @@
+services:
+ web:
+ build:
+ context: ..
+ dockerfile: docker/Dockerfile
+ ports:
+ - "8080:80"
+ volumes:
+ - ..:/usr/share/nginx/html
+ restart: unless-stopped
diff --git a/images/icons/meetup-alt.svg b/images/icons/meetup-alt.svg
new file mode 100644
index 00000000..0ece1960
--- /dev/null
+++ b/images/icons/meetup-alt.svg
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/images/icons/meetup.svg b/images/icons/meetup.svg
new file mode 100644
index 00000000..3cb6e5b2
--- /dev/null
+++ b/images/icons/meetup.svg
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/images/icons/signal-alt.svg b/images/icons/signal-alt.svg
new file mode 100644
index 00000000..a2740ea3
--- /dev/null
+++ b/images/icons/signal-alt.svg
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/images/icons/unsplash.svg b/images/icons/unsplash.svg
old mode 100755
new mode 100644
diff --git a/index.html b/index.html
index 1e8996ec..b1863923 100644
--- a/index.html
+++ b/index.html
@@ -1,6 +1,6 @@
-
LittleLink
-
+
-
+
-
+
-
+
-
+
+
+
+
+
@@ -41,7 +46,7 @@
-
-
LittleLink
+
LittleLink
Amazon Wishlist
-
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
@@ -94,7 +99,7 @@
Behance
-
+
Bluesky
@@ -178,7 +183,7 @@
Kick
-
+
Kickstarter
@@ -212,12 +217,18 @@
Medium
+
+
Meetup
+
+
+
Meetup
+
Get it from Microsoft
-
+
Notion
-
+
Obsidian
@@ -245,6 +256,9 @@
Signal
+
+
Signal
+
Join Slack
@@ -370,7 +384,7 @@
Visit Our Shop
-
+
10% Discount
@@ -380,13 +394,16 @@
Visit Website
+
+
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
+