diff --git a/README.md b/README.md
index 884e80a02b..72144cdeff 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,7 @@ The AI-native Git platform. Self-hosted, fast, and designed for the age of AI-as
- [AI Features Configuration](#ai-features-configuration)
- [Authentication Sources](#authentication-sources)
- [Email/SMTP Setup](#emailsmtp-setup)
+ - [Unsplash Integration](#unsplash-integration)
- [Usage](#usage)
- [Repository Operations](#repository-operations)
- [Project Management](#project-management)
@@ -549,6 +550,25 @@ USER = your-email@gmail.com
PASSWD = your-app-password
```
+### Unsplash Integration
+
+Enable Unsplash image search for repository social card backgrounds (Media Kit). This allows repository admins to search and select high-quality background images directly from Unsplash.
+
+```ini
+[unsplash]
+; Enable Unsplash integration for Media Kit background images
+ENABLED = true
+; Unsplash API access key (get one at https://unsplash.com/developers)
+ACCESS_KEY = your_unsplash_access_key
+```
+
+To obtain an access key:
+1. Create an account at [unsplash.com/developers](https://unsplash.com/developers)
+2. Create a new application
+3. Copy the **Access Key** (the Secret Key is not needed)
+
+When enabled, repository admins can search Unsplash from **Settings > Media Kit** when using the "Background Image" social card style. Attribution is handled automatically per Unsplash API guidelines.
+
## Usage
### Repository Operations
diff --git a/modules/socialcard/socialcard.go b/modules/socialcard/socialcard.go
index e7b0e3ae98..db981a8c34 100644
--- a/modules/socialcard/socialcard.go
+++ b/modules/socialcard/socialcard.go
@@ -378,9 +378,9 @@ func (r *Renderer) renderImageCard(data CardData) ([]byte, error) {
fillRect(img, img.Bounds(), ThemeDark.Background)
}
- // Strong gradient scrim over bottom 45%
- scrimStartY := h * 55 / 100
- drawGradientScrim(img, scrimStartY, h, 220)
+ // Strong gradient scrim over bottom 50%
+ scrimStartY := h * 50 / 100
+ drawGradientScrim(img, scrimStartY, h, 240)
// White text on scrim
titleColor := color.RGBA{R: 255, G: 255, B: 255, A: 255}
diff --git a/templates/base/head_opengraph.tmpl b/templates/base/head_opengraph.tmpl
index 89f446424b..08f177e99a 100644
--- a/templates/base/head_opengraph.tmpl
+++ b/templates/base/head_opengraph.tmpl
@@ -33,8 +33,13 @@
{{end}}
+ {{if or (eq .Repository.SocialCardTheme "solid") (eq .Repository.SocialCardTheme "image")}}
+
+
+ {{else}}
+ {{end}}
diff --git a/templates/repo/settings/media_kit.tmpl b/templates/repo/settings/media_kit.tmpl
index a6838ff263..48b1d7667d 100644
--- a/templates/repo/settings/media_kit.tmpl
+++ b/templates/repo/settings/media_kit.tmpl
@@ -200,6 +200,11 @@
el.style.borderColor = 'var(--color-primary)';
el.style.opacity = '0.7';
el.style.pointerEvents = 'none';
+ // Show a status message
+ const statusEl = document.createElement('div');
+ statusEl.textContent = 'Downloading imageā¦';
+ statusEl.style.cssText = 'padding:8px;color:var(--color-text);font-size:13px;';
+ resultsDiv.parentNode.insertBefore(statusEl, resultsDiv.nextSibling);
try {
const csrf = document.querySelector('input[name="_csrf"]').value;
const fd = new FormData();
@@ -209,16 +214,15 @@
fd.append('url', photo.regular);
const resp = await fetch('{{.Link}}/unsplash/select', {method: 'POST', body: fd});
if (resp.ok) {
- // Switch radio to "image" and update UI
- const imageRadio = document.querySelector('.style-radio[value="image"]');
- if (imageRadio) {
- imageRadio.checked = true;
- updateButtonStyles();
- solidOpts.style.display = 'none';
- imageOpts.style.display = '';
- }
- updatePreview();
+ statusEl.textContent = 'Image saved! Reloadingā¦';
+ window.location.reload();
+ } else {
+ statusEl.textContent = 'Failed to save image. Please try again.';
+ statusEl.style.color = 'var(--color-error)';
}
+ } catch (e) {
+ statusEl.textContent = 'Network error. Please try again.';
+ statusEl.style.color = 'var(--color-error)';
} finally {
el.style.opacity = '1';
el.style.pointerEvents = '';