Markdown论坛阅读器 - 轻量级知识管理解决方案
项目简介
Markdown论坛阅读器是一个优雅、高效的单文件PHP应用程序,专门用于将Markdown格式的技术讨论、博客文章或论坛内容以美观的网页形式呈现。它完美结合了简洁的部署方式和强大的功能特性,是个人知识管理和小型团队内容分享的理想选择。
核心特性
🚀 一键部署
- 单文件实现,无需复杂配置
- 零数据库依赖,直接读取Markdown文件
- 支持主流PHP环境(PHP 5.6+)
📝 完美Markdown支持
- 完整支持CommonMark标准
- 代码语法高亮(集成highlight.js)
- 表格、列表、引用等丰富格式
- 图片自适应和标题说明
🧮 数学公式渲染
- 集成MathJax引擎
- 支持LaTeX数学公式语法
- 行内公式:
$E = mc^2$ - 块级公式:
$$\sum_{i=1}^n i = \frac{n(n+1)}{2}$$
🔍 智能全文搜索
- 实时内容检索
- 标题和内容权重匹配
- 搜索结果高亮显示
- 相关度智能排序
📅 时间线归档
- 按年份、月份自动分组
- 清晰的时间线浏览体验
- 快速导航跳转
🎨 响应式设计
- 移动端友好适配
- 现代化UI界面
- 可自定义主题色彩
- 优雅的动画交互
快速开始
环境要求
- PHP 5.6 或更高版本
- Web服务器(Apache/Nginx)或PHP内置服务器
- 支持Markdown文件存储的目录权限
安装步骤
- 下载必要文件
下载主程序
将下面代码保存为index.php,上传到服务器根目录
下载Parsedown解析器
去https://parsedown.org/网站下载 Parsedown.php,上传到服务器根目录
创建内容目录
mkdir bbs
-
配置基础信息 在
index.php中修改配置常量:define('MD_DIR', __DIR__ . '/bbs'); // 内容目录 define('SITE_TITLE', '我的技术论坛'); // 网站标题 define('THEME_COLOR', '#2563eb'); // 主题色彩 -
添加内容文件 在
bbs目录中创建Markdown文件:--- title: 我的第一篇文章 date: 2024-01-15 author: 作者姓名 tags: PHP,Markdown,教程 --- ## 文章内容 这是我的第一篇技术文章,支持**粗体**、*斜体*等格式。 -
启动服务
# 使用PHP内置服务器 php -S localhost:8000
或配置到Apache/Nginx
## 内容管理指南
### 文件命名规范
- 使用有意义的英文或拼音命名
- 扩展名必须为`.md`
- 示例:`php-tutorial.md`、`mysql-optimization.md`
### 元数据格式
每篇文章可选的YAML front matter:
```yaml
---
title: 文章标题
date: 2024-01-15 # 发布日期
author: 作者名称 # 文章作者
tags: 标签1,标签2,标签3 # 文章标签(逗号分隔)
---
数学公式使用
行内公式:$\nabla \cdot \mathbf{E} = \frac{\rho}{\epsilon_0}$
块级公式:
$$
f(x) = \int_{-\infty}^{\infty} \hat f(\xi) e^{2 \pi i \xi x} d\xi
$$
高级功能
搜索优化
- 搜索词自动高亮
- 标题匹配权重更高
- 智能摘要生成
自定义样式
通过修改CSS变量轻松定制:
:root {
--primary: #your-color; /* 主色调 */
--secondary: #64748b; /* 次要色 */
--accent: #f1f5f9; /* 强调色 */
}
性能特性
- 文件缓存机制
- 按需内容加载
- 最小化资源请求
使用场景
🏢 技术团队知识库
- 代码规范文档
- 技术方案讨论
- 项目经验分享
🎓 教育机构
- 课程讲义发布
- 学术论文展示
- 数学公式演示
👨💻 个人博客
- 技术博客管理
- 学习笔记整理
- 项目文档维护
🔬 科研团队
- 研究论文分享
- 实验数据说明
- 学术讨论记录
优势对比
| 特性 | 传统论坛 | 本阅读器 |
|---|---|---|
| 部署复杂度 | 高 | 极低 |
| 内容格式 | 有限 | 丰富Markdown |
| 数学公式 | 不支持 | 完美支持 |
| 搜索功能 | 基础 | 智能全文搜索 |
| 维护成本 | 高 | 几乎为零 |
| 数据安全 | 数据库依赖 | 纯文件存储 |
技术亮点
- 架构优雅:MVC模式的单文件实现
- 扩展性强:模块化设计便于功能扩展
- 安全可靠:内容安全过滤和验证
- 性能优异:轻量级代码,快速响应
- 标准兼容:遵循Web标准和Markdown规范
结语
Markdown论坛阅读器重新定义了轻量级内容管理的标准。无论是个人技术博客、团队知识分享,还是学术内容展示,它都能提供专业级的阅读体验和便捷的内容管理。零配置部署、全功能覆盖、优雅体验——让您专注于内容创作,而非技术维护。
立即开始使用,打造属于您的专业知识分享平台!
代码分享:
<?php
/**
* Markdown论坛阅读器 - 单文件实现
* 支持公式渲染、全文搜索、按年月分组
*/
// 配置设置
define('MD_DIR', __DIR__ . '/bbs'); // Markdown文件目录
define('SITE_TITLE', 'Markdown论坛阅读器');
define('THEME_COLOR', '#2563eb');
date_default_timezone_set('Asia/Shanghai');
// 引入Parsedown Markdown解析器
require_once 'Parsedown.php';
// 获取请求参数
$page = $_GET['page'] ?? 'home';
$id = $_GET['id'] ?? '';
$query = $_GET['q'] ?? '';
$year = $_GET['year'] ?? '';
$month = $_GET['month'] ?? '';
// 主应用逻辑
$app = new MarkdownReader();
$app->handleRequest($page, $id, $query, $year, $month);
class MarkdownReader {
private $articles = [];
private $groupedArticles = [];
private $parsedown;
public function __construct() {
// 初始化Parsedown
$this->parsedown = new Parsedown();
$this->parsedown->setSafeMode(false); // 允许HTML,因为我们信任自己的内容
$this->parsedown->setMarkupEscaped(false); // 不转义标记
$this->loadArticles();
$this->groupArticlesByYearMonth();
}
public function handleRequest($page, $id, $query, $year, $month) {
// 路由处理
if (!empty($query)) {
$this->renderSearchResults($query);
} elseif (!empty($id) && isset($this->articles[$id])) {
$this->renderArticle($id);
} elseif (!empty($year) && !empty($month)) {
$this->renderArchive($year, $month);
} elseif (!empty($year)) {
$this->renderYearArchive($year);
} else {
$this->renderHomepage();
}
}
private function loadArticles() {
$files = glob(MD_DIR . '/*.md');
foreach ($files as $file) {
$id = basename($file, '.md');
$content = file_get_contents($file);
$metadata = $this->extractMetadata($content);
$content = $this->removeMetadata($content);
$this->articles[$id] = [
'id' => $id,
'title' => $metadata['title'] ?? $id,
'content' => $content,
'date' => $metadata['date'] ?? date('Y-m-d', filemtime($file)),
'author' => $metadata['author'] ?? '未知作者',
'tags' => isset($metadata['tags']) ? explode(',', $metadata['tags']) : []
];
}
// 按日期排序
uasort($this->articles, function($a, $b) {
return strtotime($b['date']) - strtotime($a['date']);
});
}
private function groupArticlesByYearMonth() {
foreach ($this->articles as $id => $article) {
$timestamp = strtotime($article['date']);
$year = date('Y', $timestamp);
$month = date('m', $timestamp);
$monthName = date('F Y', $timestamp);
if (!isset($this->groupedArticles[$year])) {
$this->groupedArticles[$year] = [
'year' => $year,
'count' => 0,
'months' => []
];
}
if (!isset($this->groupedArticles[$year]['months'][$month])) {
$this->groupedArticles[$year]['months'][$month] = [
'month' => $month,
'month_name' => $monthName,
'count' => 0,
'articles' => []
];
}
$this->groupedArticles[$year]['months'][$month]['articles'][$id] = $article;
$this->groupedArticles[$year]['months'][$month]['count']++;
$this->groupedArticles[$year]['count']++;
}
}
private function extractMetadata(&$content) {
$metadata = [];
$lines = explode("\n", $content);
if (count($lines) > 0 && trim($lines[0]) === '---') {
array_shift($lines);
while (count($lines) > 0 && trim($lines[0]) !== '---') {
$line = array_shift($lines);
if (preg_match('/^(\w+):\s*(.+)$/', $line, $matches)) {
$metadata[strtolower($matches[1])] = trim($matches[2]);
}
}
array_shift($lines);
$content = implode("\n", $lines);
}
return $metadata;
}
private function removeMetadata($content) {
$lines = explode("\n", $content);
if (count($lines) > 0 && trim($lines[0]) === '---') {
array_shift($lines);
while (count($lines) > 0 && trim($lines[0]) !== '---') {
array_shift($lines);
}
array_shift($lines);
return implode("\n", $lines);
}
return $content;
}
private function parseMarkdown($content) {
// 使用Parsedown解析Markdown
$htmlContent = $this->parsedown->text($content);
// 不再使用正则表达式处理数学公式,交给MathJax处理
return $htmlContent;
}
private function getExcerpt($content, $length = 150) {
$content = strip_tags($content);
if (strlen($content) > $length) {
$content = substr($content, 0, $length) . '...';
}
return $content;
}
private function searchArticles($query) {
$results = [];
foreach ($this->articles as $id => $article) {
$content = strip_tags($article['content']);
$title = $article['title'];
// 简单的内容搜索
$titleScore = substr_count(strtolower($title), strtolower($query)) * 10;
$contentScore = substr_count(strtolower($content), strtolower($query));
$totalScore = $titleScore + $contentScore;
if ($totalScore > 0) {
$snippet = $this->extractSnippet($content, $query);
$results[$id] = [
'article' => $article,
'score' => $totalScore,
'snippet' => $snippet
];
}
}
// 按匹配度排序
uasort($results, function($a, $b) {
return $b['score'] - $a['score'];
});
return $results;
}
private function extractSnippet($content, $query, $length = 200) {
$position = stripos($content, $query);
if ($position === false) {
return substr($content, 0, $length) . '...';
}
$start = max(0, $position - $length / 2);
$snippet = substr($content, $start, $length);
if ($start > 0) {
$snippet = '...' . $snippet;
}
if ($start + $length < strlen($content)) {
$snippet = $snippet . '...';
}
// 高亮搜索词
$snippet = preg_replace("/(" . preg_quote($query, '/') . ")/i", '<mark>$1</mark>', $snippet);
return $snippet;
}
private function renderHomepage() {
$this->renderHeader('首页');
echo '
<div class="container">
<header class="page-header">
<h1>'.SITE_TITLE.'</h1>
<p>收录各类技术讨论精华文章</p>
</header>
<div class="search-box">
<form action="" method="GET">
<input type="text" name="q" placeholder="搜索文章..." value="">
<button type="submit">搜索</button>
</form>
</div>
<div class="archive-container">';
foreach ($this->groupedArticles as $year => $yearData) {
echo '
<section class="year-section">
<h2 id="year-'.$year.'">'.$year.'年</h2>';
foreach ($yearData['months'] as $month => $monthData) {
echo '
<div class="month-section">
<h3>'.$monthData['month_name'].'</h3>
<div class="articles-list">';
foreach ($monthData['articles'] as $id => $article) {
echo '
<article class="article-card">
<h4><a href="?page=article&id='.$article['id'].'">'.$article['title'].'</a></h4>
<div class="article-meta">
<time datetime="'.$article['date'].'">'.date('Y年m月d日', strtotime($article['date'])).'</time>
<span>•</span>
<span>'.$article['author'].'</span>
</div>
<p class="article-excerpt">'.$this->getExcerpt($article['content'], 150).'</p>
<a href="?page=article&id='.$article['id'].'" class="read-more">阅读全文</a>
</article>';
}
echo '
</div>
</div>';
}
echo '
</section>';
}
echo '
</div>
</div>';
$this->renderFooter();
}
private function renderArticle($id) {
$article = $this->articles[$id];
$htmlContent = $this->parseMarkdown($article['content']);
$this->renderHeader($article['title']);
echo '
<div class="container">
<article class="article-full">
<header class="article-header">
<h1>'.$article['title'].'</h1>
<div class="article-meta">
<time datetime="'.$article['date'].'">'.date('Y年m月d日', strtotime($article['date'])).'</time>
<span>•</span>
<span>'.$article['author'].'</span>
</div>
</header>
<div class="article-content">
'.$htmlContent.'
</div>
<footer class="article-footer">
<a href="./" class="back-link">← 返回首页</a>
</footer>
</article>
</div>';
$this->renderFooter();
}
private function renderSearchResults($query) {
$results = $this->searchArticles($query);
$this->renderHeader('搜索: ' . $query);
echo '
<div class="container">
<header class="page-header">
<h1>搜索: '.htmlspecialchars($query).'</h1>
<p>找到 '.count($results).' 条结果</p>
</header>
<div class="search-results">';
if (count($results) > 0) {
foreach ($results as $result) {
$article = $result['article'];
echo '
<article class="search-result">
<h3><a href="?page=article&id='.$article['id'].'">'.$article['title'].'</a></h3>
<div class="article-meta">
<time datetime="'.$article['date'].'">'.date('Y年m月d日', strtotime($article['date'])).'</time>
<span>•</span>
<span>'.$article['author'].'</span>
</div>
<p>'.$result['snippet'].'</p>
</article>';
}
} else {
echo '<p class="no-results">没有找到相关结果</p>';
}
echo '
</div>
</div>';
$this->renderFooter();
}
private function renderHeader($title = '') {
$fullTitle = $title ? $title . ' - ' . SITE_TITLE : SITE_TITLE;
echo '<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>'.htmlspecialchars($fullTitle).'</title>
<script>
window.MathJax = {
tex: {
inlineMath: [[\'$\', \'$\']],
displayMath: [[\'$$\', \'$$\']],
processEscapes: true,
processEnvironments: true
},
options: {
skipHtmlTags: [\'script\', \'noscript\', \'style\', \'textarea\', \'pre\'],
ignoreHtmlClass: \'no-mathjax\'
}
};
</script>
<script src="https://cdn.bootcss.com/mathjax/2.7.7/MathJax.js?config=TeX-AMS-MML_HTMLorMML-full"></script><link rel="stylesheet" href="/js/styles/tomorrow-night.css" /><script src="/js/highlight.pack.js"></script><script type="text/javascript" language="javascript">hljs.initHighlightingOnLoad();</script>
<style>
:root {
--primary: '.THEME_COLOR.';
--primary-light: '.$this->adjustColorLightness(THEME_COLOR, 0.8).';
--secondary: #64748b;
--accent: #f1f5f9;
--light: #f8f9fa;
--dark: #212529;
--gray: #6c757d;
--border: #e9ecef;
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--code-font: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
--line-height: 1.2;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font-family);
line-height: var(--line-height);
color: var(--dark);
background-color: #fff;
padding-top: 70px;
}
a {
color: var(--primary);
text-decoration: none;
transition: color 0.2s ease;
}
a:hover {
color: var(--primary);
text-decoration: underline;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
/* 导航栏样式 */
.navbar {
background: white;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
padding: 0.8rem 0;
backdrop-filter: blur(8px);
background-color: rgba(255, 255, 255, 0.95);
}
.nav-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.nav-logo {
font-size: 1.5rem;
font-weight: bold;
color: var(--primary);
}
.nav-menu {
display: flex;
list-style: none;
}
.nav-item {
margin-left: 1.5rem;
position: relative;
}
.nav-link {
color: var(--dark);
font-weight: 500;
}
.nav-link:hover {
color: var(--primary);
}
/* 页面标题 */
.page-header {
text-align: center;
margin-bottom: 2rem;
padding: 1.5rem 0;
}
.page-header h1 {
font-size: 2.2rem;
margin-bottom: 0.5rem;
color: var(--dark);
}
.page-header p {
font-size: 1.1rem;
color: var(--gray);
}
/* 搜索框 */
.search-box {
max-width: 600px;
margin: 0 auto 2.5rem;
}
.search-box form {
display: flex;
}
.search-box input {
flex: 1;
padding: 0.75rem 1rem;
border: 1px solid var(--border);
border-radius: 6px 0 0 6px;
font-size: 1rem;
font-family: var(--font-family);
transition: border-color 0.2s ease;
}
.search-box input:focus {
outline: none;
border-color: var(--primary);
}
.search-box button {
padding: 0.75rem 1.5rem;
background: var(--primary);
color: white;
border: none;
border-radius: 0 6px 6px 0;
cursor: pointer;
font-size: 1rem;
font-family: var(--font-family);
transition: background-color 0.2s ease;
}
.search-box button:hover {
background-color: '.$this->adjustColorLightness(THEME_COLOR, -0.1).';
}
/* 文章列表 */
.archive-container {
margin-bottom: 2.5rem;
}
.year-section {
margin-bottom: 2.5rem;
}
.year-section h2 {
font-size: 1.6rem;
margin-bottom: 1.2rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid var(--accent);
color: var(--dark);
}
.month-section {
margin-bottom: 1.8rem;
}
.month-section h3 {
font-size: 1.3rem;
margin-bottom: 0.8rem;
color: var(--secondary);
}
.articles-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1.2rem;
}
.article-card {
background: white;
border-radius: 10px;
padding: 1.4rem;
box-shadow: 0 3px 12px rgba(0,0,0,0.06);
transition: transform 0.2s, box-shadow 0.2s;
border: 1px solid var(--border);
}
.article-card:hover {
transform: translateY(-3px);
box-shadow: 0 6px 18px rgba(0,0,0,0.1);
}
.article-card h4 {
font-size: 1.15rem;
margin-bottom: 0.6rem;
line-height: 1.3;
}
.article-meta {
font-size: 0.85rem;
color: var(--gray);
margin-bottom: 0.8rem;
display: flex;
align-items: center;
gap: 0.4rem;
}
.article-excerpt {
color: var(--secondary);
margin-bottom: 1rem;
font-size: 0.95rem;
line-height: 1.4;
}
.read-more {
font-weight: 500;
font-size: 0.9rem;
display: inline-flex;
align-items: center;
gap: 0.3rem;
}
.read-more:hover {
text-decoration: none;
color: '.$this->adjustColorLightness(THEME_COLOR, -0.2).';
}
/* 文章详情页 */
.article-full {
max-width: 800px;
margin: 0 auto;
}
.article-header {
text-align: center;
margin-bottom: 1.8rem;
padding-bottom: 1.5rem;
border-bottom: 1px solid var(--border);
}
.article-header h1 {
font-size: 2rem;
margin-bottom: 0.8rem;
line-height: 1.3;
}
.article-content {
line-height: 1.4;
font-size: 1.05rem;
}
.article-content h1, .article-content h2, .article-content h3 {
margin: 1.8rem 0 1rem;
color: var(--dark);
line-height: 1.3;
}
.article-content h1 {
font-size: 1.8rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--border);
}
.article-content h2 {
font-size: 1.5rem;
}
.article-content h3 {
font-size: 1.3rem;
}
.article-content p {
margin-bottom: 1.2rem;
line-height: 1.4;
}
.article-content ul, .article-content ol {
margin-bottom: 1.2rem;
padding-left: 1.5rem;
}
.article-content li {
margin-bottom: 0.4rem;
}
.article-content pre {
background: #f8f9fa;
border-radius: 6px;
overflow-x: auto;
padding: 1.2rem;
margin: 1.2rem 0;
border: 1px solid var(--border);
font-family: var(--code-font);
font-size: 0.9rem;
line-height: 1.4;
}
.article-content code {
background: #f8f9fa;
padding: 0.2rem 0.4rem;
border-radius: 4px;
font-family: var(--code-font);
font-size: 0.9rem;
}
.article-content .inline-code {
background: #f1f3f5;
padding: 0.15rem 0.4rem;
border-radius: 4px;
font-size: 0.85rem;
color: #e67700;
}
.article-content blockquote {
border-left: 4px solid var(--primary);
padding: 0.8rem 1rem;
margin: 1.2rem 0;
background-color: var(--primary-light);
border-radius: 0 6px 6px 0;
}
.article-content blockquote p {
margin-bottom: 0;
color: #495057;
}
.md-link {
color: var(--primary);
text-decoration: underline;
text-underline-offset: 2px;
}
.md-image {
margin: 1.5rem 0;
text-align: center;
}
.md-image img {
max-width: 100%;
height: auto;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.image-caption {
display: block;
margin-top: 0.6rem;
font-size: 0.9rem;
color: var(--gray);
font-style: italic;
}
/* MathJax样式调整 */
.MathJax {
font-size: 1.1em !important;
}
.mjx-chtml {
line-height: 1.2 !important;
}
.article-footer {
margin-top: 2.5rem;
padding-top: 1.5rem;
border-top: 1px solid var(--border);
}
.back-link {
display: inline-flex;
align-items: center;
gap: 0.4rem;
font-weight: 500;
padding: 0.6rem 1rem;
background: var(--accent);
border-radius: 6px;
transition: background-color 0.2s ease;
}
.back-link:hover {
background: #e9ecef;
text-decoration: none;
}
/* 搜索页面 */
.search-results {
max-width: 800px;
margin: 0 auto;
}
.search-result {
margin-bottom: 1.8rem;
padding-bottom: 1.8rem;
border-bottom: 1px solid var(--border);
}
.search-result h3 {
font-size: 1.2rem;
margin-bottom: 0.5rem;
color: var(--dark);
}
.search-result .article-meta {
margin-bottom: 0.5rem;
}
.no-results {
text-align: center;
color: var(--gray);
font-size: 1.1rem;
margin: 2.5rem 0;
}
/* 代码高亮增强 */
pre code.hljs {
padding: 0;
background: transparent;
}
.language-php, .language-js, .language-css, .language-html, .language-py {
position: relative;
}
.language-php::before, .language-js::before, .language-css::before, .language-html::before, .language-py::before {
content: attr(class);
position: absolute;
top: 0;
right: 0;
padding: 0.2rem 0.6rem;
font-size: 0.7rem;
background: var(--primary);
color: white;
border-radius: 0 6px 0 6px;
}
/* 响应式设计 */
@media (max-width: 768px) {
body {
padding-top: 60px;
}
.nav-menu {
display: none;
}
.articles-list {
grid-template-columns: 1fr;
}
.page-header h1 {
font-size: 1.8rem;
}
.article-header h1 {
font-size: 1.6rem;
}
.article-content {
font-size: 1rem;
}
.article-content h1 {
font-size: 1.5rem;
}
.article-content h2 {
font-size: 1.3rem;
}
.article-content h3 {
font-size: 1.15rem;
}
/* 移动端MathJax调整 */
.MathJax {
font-size: 1em !important;
}
mjx-container {
overflow-x: auto;
overflow-y: hidden;
}
}
</style>
</head>
<body>
<nav class="navbar">
<div class="nav-container">
<a href="./" class="nav-logo">'.SITE_TITLE.'</a>
<ul class="nav-menu">
<li class="nav-item"><a href="./" class="nav-link">首页</a></li>';
foreach ($this->groupedArticles as $year => $yearData) {
echo '<li class="nav-item"><a href="#year-'.$year.'" class="nav-link">'.$year.'年</a></li>';
}
echo '
</ul>
</div>
</nav>';
}
private function renderFooter() {
echo '
</body>
</html>';
}
private function adjustColorLightness($color, $amount) {
// 调整颜色亮度的辅助函数
$hex = str_replace('#', '', $color);
if (strlen($hex) == 3) {
$hex = str_repeat(substr($hex,0,1), 2).str_repeat(substr($hex,1,1), 2).str_repeat(substr($hex,2,1), 2);
}
$r = hexdec(substr($hex,0,2));
$g = hexdec(substr($hex,2,2));
$b = hexdec(substr($hex,4,2));
$r = max(0, min(255, $r + $amount * 255));
$g = max(0, min(255, $g + $amount * 255));
$b = max(0, min(255, $b + $amount * 255));
return '#'.str_pad(dechex($r), 2, '0', STR_PAD_LEFT)
.str_pad(dechex($g), 2, '0', STR_PAD_LEFT)
.str_pad(dechex($b), 2, '0', STR_PAD_LEFT);
}
}
?>