update
This commit is contained in:
@@ -0,0 +1,16 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.{json,yml,yaml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"extends": [
|
||||||
|
"plugin:@wordpress/eslint-plugin/recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended"
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2020,
|
||||||
|
"sourceType": "module",
|
||||||
|
"project": "./tsconfig.json"
|
||||||
|
},
|
||||||
|
"plugins": ["@typescript-eslint"],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
|
||||||
|
"@typescript-eslint/explicit-function-return-type": "warn",
|
||||||
|
"no-console": "warn"
|
||||||
|
},
|
||||||
|
"ignorePatterns": ["vendor/", "node_modules/", "assets/", "dist/"]
|
||||||
|
}
|
||||||
+22
@@ -102,3 +102,25 @@ dist
|
|||||||
|
|
||||||
# TernJS port file
|
# TernJS port file
|
||||||
.tern-port
|
.tern-port
|
||||||
|
|
||||||
|
# -----------------------------------------------
|
||||||
|
# CyyWordpress additions
|
||||||
|
# -----------------------------------------------
|
||||||
|
# Build output
|
||||||
|
assets/js/
|
||||||
|
assets/css/
|
||||||
|
assets/.vite/
|
||||||
|
*.map
|
||||||
|
|
||||||
|
# Packaged theme zip
|
||||||
|
*.zip
|
||||||
|
|
||||||
|
# OS / editor
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
.idea/
|
||||||
|
.vscode/settings.json
|
||||||
|
|
||||||
|
# Composer: vendor is committed for deployment, but generated files are not
|
||||||
|
composer.lock
|
||||||
|
|
||||||
|
|||||||
+11
-14
@@ -1,16 +1,13 @@
|
|||||||
{
|
{
|
||||||
"extends": [
|
"extends": ["stylelint-config-standard-scss"],
|
||||||
"stylelint-config-wordpress/scss"
|
"rules": {
|
||||||
],
|
"color-named": "never",
|
||||||
"ignoreFiles": [
|
"declaration-no-important": true,
|
||||||
"sass/_normalize.scss"
|
"max-nesting-depth": 3,
|
||||||
],
|
"selector-class-pattern": "^[a-z][a-z0-9-_]*(__[a-z0-9-_]+)?(--[a-z0-9-_]+)?$",
|
||||||
"rules": {
|
"scss/dollar-variable-pattern": "^[a-z][a-z0-9-_]*$",
|
||||||
"font-family-no-missing-generic-family-keyword": null,
|
"scss/at-mixin-pattern": "^[a-z][a-z0-9-_]*$"
|
||||||
"no-descending-specificity": null,
|
},
|
||||||
"block-no-empty": null,
|
"ignoreFiles": ["node_modules/**", "vendor/**", "assets/**"]
|
||||||
"no-duplicate-selectors": null,
|
|
||||||
"font-family-no-duplicate-names": null,
|
|
||||||
"selector-class-pattern": null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,125 @@
|
|||||||
# CyyWordpress
|
# CyyWordpress
|
||||||
一个自用的Wordpress主题
|
|
||||||
|
一个基于 **Bulma 1.x** + **Underscores (_s)** 的现代 WordPress 主题,专为中文博客场景设计。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 环境要求
|
||||||
|
|
||||||
|
| 依赖 | 最低版本 |
|
||||||
|
|------|---------|
|
||||||
|
| PHP | 8.1 |
|
||||||
|
| WordPress | 6.0 |
|
||||||
|
| Node.js | 20(见 `.nvmrc`)|
|
||||||
|
| npm | 10+ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 本地开发环境配置
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 安装正确的 Node.js 版本(推荐使用 nvm)
|
||||||
|
nvm use # 读取 .nvmrc 自动切换到 Node 20
|
||||||
|
|
||||||
|
# 2. 安装前端依赖
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 3. 启动 Vite 开发服务器(HMR)
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# 4. 生产构建(含 contenthash 文件指纹)
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 5. 生成 RTL 样式
|
||||||
|
npm run build:rtl
|
||||||
|
```
|
||||||
|
|
||||||
|
> **注意:** 开发时,将 WordPress 的 `WP_HOME` 指向 `http://localhost:3000`,
|
||||||
|
> Vite 会代理所有请求到本地 WordPress 实例并启用热更新。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 构建流程说明
|
||||||
|
|
||||||
|
| 命令 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `npm run dev` | 启动 Vite 开发服务器(端口 3000,支持 HMR)|
|
||||||
|
| `npm run build` | 生产构建,输出到 `assets/`(含 contenthash)|
|
||||||
|
| `npm run build:rtl` | 生成 `assets/css/main-rtl.css` |
|
||||||
|
| `npm run lint` | 运行 ESLint + StyleLint |
|
||||||
|
| `npm run type-check` | TypeScript 类型检查(不生成文件)|
|
||||||
|
|
||||||
|
构建产物由 `inc/class-cyywordpress-assets.php` 读取 `assets/.vite/manifest.json`
|
||||||
|
自动解析带 hash 的文件名注入到 `wp_enqueue_*`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SCSS 变量覆盖(子主题)
|
||||||
|
|
||||||
|
在子主题的入口 SCSS 文件中,在 `@use 'cyywordpress/scss/abstracts/variables'` **之前**
|
||||||
|
重新声明你希望覆盖的 CSS 自定义属性:
|
||||||
|
|
||||||
|
```scss
|
||||||
|
:root {
|
||||||
|
--color-primary: #6366f1; /* 换成紫色主题 */
|
||||||
|
--color-primary-dark: #4f46e5;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
所有组件均使用 `var(--color-primary)` 引用,覆盖后全局生效,无需修改源文件。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Customizer 可配置项
|
||||||
|
|
||||||
|
后台路径:**外观 → 自定义 → 首页横幅**
|
||||||
|
|
||||||
|
| 选项 | 说明 | 默认值 |
|
||||||
|
|------|------|--------|
|
||||||
|
| 欢迎横幅文字 | 首页/文章列表顶部的通知文字 | `欢迎来到这!Hello everyone.` |
|
||||||
|
| 显示欢迎横幅 | 是否在首页显示横幅 | 勾选 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 导航菜单位置
|
||||||
|
|
||||||
|
| 位置标识 | 显示位置 |
|
||||||
|
|---------|---------|
|
||||||
|
| `menu-1` | 顶部主导航 |
|
||||||
|
| `menu-2` | 页脚导航 |
|
||||||
|
| `social` | 页脚社交媒体图标链接 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Widget 区域
|
||||||
|
|
||||||
|
| 区域 ID | 位置 |
|
||||||
|
|---------|------|
|
||||||
|
| `sidebar-1` | 文章/页面侧边栏 |
|
||||||
|
| `sidebar-footer-1` | 页脚左列 |
|
||||||
|
| `sidebar-footer-2` | 页脚中列 |
|
||||||
|
| `sidebar-footer-3` | 页脚右列 |
|
||||||
|
| `sidebar-header` | 导航栏右侧 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 块模式 (Block Patterns)
|
||||||
|
|
||||||
|
注册在 `patterns/` 目录,可在编辑器 **插入块 → 模式 → CyyWordpress** 中使用:
|
||||||
|
|
||||||
|
- **Hero 横幅** — 全宽渐变横幅 + CTA 按钮
|
||||||
|
- **文章卡片网格** — 三列响应式卡片布局
|
||||||
|
- **行动号召区域** — 居中标题 + 双按钮
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PHP 代码规范检查
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer install
|
||||||
|
./vendor/bin/phpcs
|
||||||
|
./vendor/bin/phpcbf # 自动修复
|
||||||
|
```
|
||||||
|
|
||||||
|
规范基于 WordPress Coding Standards + PHPCompatibility(PHP 8.1+)。
|
||||||
|
|
||||||
|
|||||||
+5
-4
@@ -15,13 +15,14 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.6"
|
"php": ">=8.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
|
"dealerdirect/phpcodesniffer-composer-installer": "^1.0.0",
|
||||||
"wptrt/wpthemereview": "^0.2.1",
|
"wptrt/wpthemereview": "^0.2.1",
|
||||||
"php-parallel-lint/php-parallel-lint": "^1.2.0",
|
"php-parallel-lint/php-parallel-lint": "^1.3.0",
|
||||||
"wp-cli/i18n-command": "^2.2.5"
|
"wp-cli/i18n-command": "^2.6.0",
|
||||||
|
"phpcompatibility/phpcompatibility-wp": "^2.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint:wpcs": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs",
|
"lint:wpcs": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs",
|
||||||
|
|||||||
+97
-64
@@ -2,7 +2,8 @@
|
|||||||
/**
|
/**
|
||||||
* The template for displaying the footer
|
* The template for displaying the footer
|
||||||
*
|
*
|
||||||
* Contains the closing of the #content div and all content after.
|
* Three-column layout: site info | footer nav (menu-2) | social links (social).
|
||||||
|
* Dynamic copyright year. All hardcoded external links removed.
|
||||||
*
|
*
|
||||||
* @link https://developer.wordpress.org/themes/basics/template-files/#template-partials
|
* @link https://developer.wordpress.org/themes/basics/template-files/#template-partials
|
||||||
*
|
*
|
||||||
@@ -10,75 +11,107 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
</div><!-- #content .site-content .container -->
|
||||||
|
|
||||||
</div>
|
</div><!-- #page .site -->
|
||||||
|
|
||||||
|
<footer id="colophon" class="site-footer footer">
|
||||||
|
<div class="container">
|
||||||
|
<div class="columns">
|
||||||
|
|
||||||
<footer class="footer">
|
<!-- Column 1: Site info + footer widget 1 -->
|
||||||
<div class="content has-text-centered">
|
<div class="column is-4">
|
||||||
<p>
|
<p class="footer-site-name">
|
||||||
<a href="<?php echo esc_url( __( 'https://wordpress.org/', 'cyywordpress' ) ); ?>">
|
<a href="<?php echo esc_url( home_url( '/' ) ); ?>" rel="home">
|
||||||
<?php
|
<?php bloginfo( 'name' ); ?>
|
||||||
/* translators: %s: CMS name, i.e. WordPress. */
|
</a>
|
||||||
printf( esc_html__( '自豪地使用 %s', 'cyywordpress' ), 'WordPress' );
|
</p>
|
||||||
?>
|
<?php
|
||||||
</a>
|
$description = get_bloginfo( 'description' );
|
||||||
|
if ( $description ) :
|
||||||
|
echo '<p class="footer-site-description">' . esc_html( $description ) . '</p>';
|
||||||
|
endif;
|
||||||
|
?>
|
||||||
|
<?php if ( is_active_sidebar( 'sidebar-footer-1' ) ) : ?>
|
||||||
|
<div class="footer-widget-area">
|
||||||
|
<?php dynamic_sidebar( 'sidebar-footer-1' ); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div><!-- .column -->
|
||||||
|
|
||||||
<span class="sep"> | </span>
|
<!-- Column 2: Footer navigation (menu-2) + widget 2 -->
|
||||||
Theme: <a href="http://chuyaoyuan.com/">cyywordpress</a> by Chuyaoyuan(自用主题).
|
<div class="column is-4">
|
||||||
</p>
|
<?php if ( has_nav_menu( 'menu-2' ) ) : ?>
|
||||||
</div>
|
<nav class="footer-nav" aria-label="<?php esc_attr_e( '页脚导航', 'cyywordpress' ); ?>">
|
||||||
</footer>
|
<?php
|
||||||
|
wp_nav_menu(
|
||||||
|
array(
|
||||||
|
'theme_location' => 'menu-2',
|
||||||
|
'menu_id' => 'footer-menu',
|
||||||
|
'depth' => 1,
|
||||||
|
'container' => false,
|
||||||
|
'menu_class' => 'footer-menu-list',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</nav>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ( is_active_sidebar( 'sidebar-footer-2' ) ) : ?>
|
||||||
|
<div class="footer-widget-area">
|
||||||
|
<?php dynamic_sidebar( 'sidebar-footer-2' ); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div><!-- .column -->
|
||||||
|
|
||||||
</div><!-- #page -->
|
<!-- Column 3: Social links (social menu) + widget 3 -->
|
||||||
|
<div class="column is-4">
|
||||||
|
<?php if ( has_nav_menu( 'social' ) ) : ?>
|
||||||
|
<nav class="social-nav" aria-label="<?php esc_attr_e( '社交媒体链接', 'cyywordpress' ); ?>">
|
||||||
|
<?php
|
||||||
|
wp_nav_menu(
|
||||||
|
array(
|
||||||
|
'theme_location' => 'social',
|
||||||
|
'menu_id' => 'social-menu',
|
||||||
|
'depth' => 1,
|
||||||
|
'container' => false,
|
||||||
|
'menu_class' => 'social-menu-list',
|
||||||
|
'link_before' => '<span class="screen-reader-text">',
|
||||||
|
'link_after' => '</span>',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</nav>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ( is_active_sidebar( 'sidebar-footer-3' ) ) : ?>
|
||||||
|
<div class="footer-widget-area">
|
||||||
|
<?php dynamic_sidebar( 'sidebar-footer-3' ); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div><!-- .column -->
|
||||||
|
|
||||||
|
</div><!-- .columns -->
|
||||||
|
</div><!-- .container -->
|
||||||
|
|
||||||
|
<div class="footer-bottom">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<p class="footer-copyright">
|
||||||
|
© <?php echo esc_html( (string) gmdate( 'Y' ) ); ?>
|
||||||
|
<a href="<?php echo esc_url( home_url( '/' ) ); ?>">
|
||||||
|
<?php bloginfo( 'name' ); ?>
|
||||||
|
</a>.
|
||||||
|
<?php
|
||||||
|
printf(
|
||||||
|
/* translators: %s: WordPress link */
|
||||||
|
esc_html__( '基于 %s 强力驱动', 'cyywordpress' ),
|
||||||
|
'<a href="https://wordpress.org/" target="_blank" rel="noopener">WordPress</a>'
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer><!-- #colophon -->
|
||||||
|
|
||||||
<?php wp_footer(); ?>
|
<?php wp_footer(); ?>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
|
|
||||||
// Get all "navbar-burger" elements
|
|
||||||
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
|
||||||
|
|
||||||
// Check if there are any navbar burgers
|
|
||||||
if ($navbarBurgers.length > 0) {
|
|
||||||
|
|
||||||
// Add a click event on each of them
|
|
||||||
$navbarBurgers.forEach( el => {
|
|
||||||
el.addEventListener('click', () => {
|
|
||||||
|
|
||||||
// Get the target from the "data-target" attribute
|
|
||||||
const target = el.dataset.target;
|
|
||||||
const $target = document.getElementById(target);
|
|
||||||
|
|
||||||
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
|
|
||||||
el.classList.toggle('is-active');
|
|
||||||
$target.classList.toggle('is-active');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 当网页向下滑动 20px 出现"返回顶部" 按钮
|
|
||||||
window.onscroll = function() {scrollFunction()};
|
|
||||||
|
|
||||||
function scrollFunction() {console.log(121);
|
|
||||||
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
|
|
||||||
document.getElementById("myBtn").style.display = "block";
|
|
||||||
} else {
|
|
||||||
document.getElementById("myBtn").style.display = "none";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 点击按钮,返回顶部
|
|
||||||
function topFunction() {
|
|
||||||
document.body.scrollTop = 0;
|
|
||||||
document.documentElement.scrollTop = 0;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
+174
-127
@@ -7,57 +7,81 @@
|
|||||||
* @package CyyWordpress
|
* @package CyyWordpress
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if ( ! defined( '_S_VERSION' ) ) {
|
// ---------------------------------------------------------------------------
|
||||||
// Replace the version number of the theme on each release.
|
// PHP & WordPress version guards
|
||||||
define( '_S_VERSION', '1.0.0' );
|
// ---------------------------------------------------------------------------
|
||||||
|
if ( version_compare( PHP_VERSION, '8.1', '<' ) ) {
|
||||||
|
add_action(
|
||||||
|
'admin_notices',
|
||||||
|
function (): void {
|
||||||
|
echo '<div class="notice notice-error"><p>'
|
||||||
|
. esc_html__( 'CyyWordpress 主题需要 PHP 8.1 或更高版本。', 'cyywordpress' )
|
||||||
|
. '</p></div>';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return; // Do not load theme in unsupported environments.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
global $wp_version;
|
||||||
|
if ( version_compare( $wp_version, '6.0', '<' ) ) {
|
||||||
|
add_action(
|
||||||
|
'admin_notices',
|
||||||
|
function (): void {
|
||||||
|
echo '<div class="notice notice-error"><p>'
|
||||||
|
. esc_html__( 'CyyWordpress 主题需要 WordPress 6.0 或更高版本。', 'cyywordpress' )
|
||||||
|
. '</p></div>';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Version constant
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
if ( ! defined( '_S_VERSION' ) ) {
|
||||||
|
define( '_S_VERSION', '2.0.0' );
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Includes – each file owns its own domain
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
require get_template_directory() . '/inc/custom-header.php';
|
||||||
|
require get_template_directory() . '/inc/template-tags.php';
|
||||||
|
require get_template_directory() . '/inc/template-functions.php';
|
||||||
|
require get_template_directory() . '/inc/customizer.php';
|
||||||
|
require get_template_directory() . '/inc/bulmapress_navwalker.php';
|
||||||
|
require get_template_directory() . '/inc/search.php';
|
||||||
|
require get_template_directory() . '/inc/pagination.php';
|
||||||
|
require get_template_directory() . '/inc/block-patterns.php';
|
||||||
|
require get_template_directory() . '/inc/class-cyywordpress-assets.php';
|
||||||
|
require get_template_directory() . '/inc/class-cyywordpress-seo.php';
|
||||||
|
require get_template_directory() . '/inc/class-cyywordpress-schema.php';
|
||||||
|
|
||||||
|
if ( defined( 'JETPACK__VERSION' ) ) {
|
||||||
|
require get_template_directory() . '/inc/jetpack.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Theme setup
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
if ( ! function_exists( 'cyywordpress_setup' ) ) :
|
if ( ! function_exists( 'cyywordpress_setup' ) ) :
|
||||||
/**
|
/**
|
||||||
* Sets up theme defaults and registers support for various WordPress features.
|
* Sets up theme defaults and registers WordPress feature support.
|
||||||
*
|
|
||||||
* Note that this function is hooked into the after_setup_theme hook, which
|
|
||||||
* runs before the init hook. The init hook is too late for some features, such
|
|
||||||
* as indicating support for post thumbnails.
|
|
||||||
*/
|
*/
|
||||||
function cyywordpress_setup() {
|
function cyywordpress_setup(): void {
|
||||||
/*
|
|
||||||
* Make theme available for translation.
|
|
||||||
* Translations can be filed in the /languages/ directory.
|
|
||||||
* If you're building a theme based on CyyWordpress, use a find and replace
|
|
||||||
* to change 'cyywordpress' to the name of your theme in all the template files.
|
|
||||||
*/
|
|
||||||
load_theme_textdomain( 'cyywordpress', get_template_directory() . '/languages' );
|
load_theme_textdomain( 'cyywordpress', get_template_directory() . '/languages' );
|
||||||
|
|
||||||
// Add default posts and comments RSS feed links to head.
|
|
||||||
add_theme_support( 'automatic-feed-links' );
|
add_theme_support( 'automatic-feed-links' );
|
||||||
|
|
||||||
/*
|
|
||||||
* Let WordPress manage the document title.
|
|
||||||
* By adding theme support, we declare that this theme does not use a
|
|
||||||
* hard-coded <title> tag in the document head, and expect WordPress to
|
|
||||||
* provide it for us.
|
|
||||||
*/
|
|
||||||
add_theme_support( 'title-tag' );
|
add_theme_support( 'title-tag' );
|
||||||
|
|
||||||
/*
|
|
||||||
* Enable support for Post Thumbnails on posts and pages.
|
|
||||||
*
|
|
||||||
* @link https://developer.wordpress.org/themes/functionality/featured-images-post-thumbnails/
|
|
||||||
*/
|
|
||||||
add_theme_support( 'post-thumbnails' );
|
add_theme_support( 'post-thumbnails' );
|
||||||
|
add_theme_support( 'responsive-embeds' );
|
||||||
|
add_theme_support( 'align-wide' );
|
||||||
|
add_theme_support( 'widgets-block-editor' );
|
||||||
|
|
||||||
// This theme uses wp_nav_menu() in one location.
|
// Editor styles – keeps back-end visually consistent with the front end.
|
||||||
register_nav_menus(
|
add_theme_support( 'editor-styles' );
|
||||||
array(
|
add_editor_style( 'assets/css/editor-style.css' );
|
||||||
'menu-1' => esc_html__( 'Primary', 'cyywordpress' ),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Switch default core markup for search form, comment form, and comments
|
|
||||||
* to output valid HTML5.
|
|
||||||
*/
|
|
||||||
add_theme_support(
|
add_theme_support(
|
||||||
'html5',
|
'html5',
|
||||||
array(
|
array(
|
||||||
@@ -71,7 +95,6 @@ if ( ! function_exists( 'cyywordpress_setup' ) ) :
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Set up the WordPress core custom background feature.
|
|
||||||
add_theme_support(
|
add_theme_support(
|
||||||
'custom-background',
|
'custom-background',
|
||||||
apply_filters(
|
apply_filters(
|
||||||
@@ -83,116 +106,140 @@ if ( ! function_exists( 'cyywordpress_setup' ) ) :
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add theme support for selective refresh for widgets.
|
|
||||||
add_theme_support( 'customize-selective-refresh-widgets' );
|
add_theme_support( 'customize-selective-refresh-widgets' );
|
||||||
|
|
||||||
/**
|
|
||||||
* Add support for core custom logo.
|
|
||||||
*
|
|
||||||
* @link https://codex.wordpress.org/Theme_Logo
|
|
||||||
*/
|
|
||||||
add_theme_support(
|
add_theme_support(
|
||||||
'custom-logo',
|
'custom-logo',
|
||||||
array(
|
array(
|
||||||
'height' => 250,
|
'height' => 80,
|
||||||
'width' => 250,
|
'width' => 80,
|
||||||
'flex-width' => true,
|
'flex-width' => true,
|
||||||
'flex-height' => true,
|
'flex-height' => true,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Navigation menus.
|
||||||
|
register_nav_menus(
|
||||||
|
array(
|
||||||
|
'menu-1' => esc_html__( '主导航', 'cyywordpress' ),
|
||||||
|
'menu-2' => esc_html__( '页脚导航', 'cyywordpress' ),
|
||||||
|
'social' => esc_html__( '社交媒体链接', 'cyywordpress' ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Custom image sizes.
|
||||||
|
add_image_size( 'card-thumbnail', 800, 450, true );
|
||||||
|
add_image_size( 'card-thumbnail-sm', 400, 225, true );
|
||||||
}
|
}
|
||||||
endif;
|
endif;
|
||||||
add_action( 'after_setup_theme', 'cyywordpress_setup' );
|
add_action( 'after_setup_theme', 'cyywordpress_setup' );
|
||||||
|
|
||||||
/**
|
// ---------------------------------------------------------------------------
|
||||||
* Set the content width in pixels, based on the theme's design and stylesheet.
|
// Content width
|
||||||
*
|
// ---------------------------------------------------------------------------
|
||||||
* Priority 0 to make it available to lower priority callbacks.
|
function cyywordpress_content_width(): void {
|
||||||
*
|
$GLOBALS['content_width'] = apply_filters( 'cyywordpress_content_width', 760 );
|
||||||
* @global int $content_width
|
|
||||||
*/
|
|
||||||
function cyywordpress_content_width() {
|
|
||||||
$GLOBALS['content_width'] = apply_filters( 'cyywordpress_content_width', 640 );
|
|
||||||
}
|
}
|
||||||
add_action( 'after_setup_theme', 'cyywordpress_content_width', 0 );
|
add_action( 'after_setup_theme', 'cyywordpress_content_width', 0 );
|
||||||
|
|
||||||
/**
|
// ---------------------------------------------------------------------------
|
||||||
* Register widget area.
|
// Widget areas
|
||||||
*
|
// ---------------------------------------------------------------------------
|
||||||
* @link https://developer.wordpress.org/themes/functionality/sidebars/#registering-a-sidebar
|
function cyywordpress_widgets_init(): void {
|
||||||
*/
|
register_sidebar(
|
||||||
function cyywordpress_widgets_init() {
|
array(
|
||||||
// register_sidebar(
|
'name' => esc_html__( '文章侧边栏', 'cyywordpress' ),
|
||||||
// array(
|
'id' => 'sidebar-1',
|
||||||
// 'name' => esc_html__( 'Sidebar', 'cyywordpress' ),
|
'description' => esc_html__( '出现在文章/页面侧边的 Widget 区域。', 'cyywordpress' ),
|
||||||
// 'id' => 'sidebar-1',
|
'before_widget' => '<div id="%1$s" class="widget card %2$s" style="margin-bottom:1rem;">',
|
||||||
// 'description' => esc_html__( 'Add widgets here.', 'cyywordpress' ),
|
'after_widget' => '</div>',
|
||||||
// 'before_widget' => '<section id="%1$s" class="widget %2$s">',
|
'before_title' => '<div class="card-header"><p class="card-header-title">',
|
||||||
// 'after_widget' => '</section>',
|
'after_title' => '</p></div><div class="card-content">',
|
||||||
// 'before_title' => '<h2 class="widget-title">',
|
)
|
||||||
// 'after_title' => '</h2>',
|
);
|
||||||
// )
|
|
||||||
// );
|
|
||||||
|
|
||||||
register_sidebar(
|
register_sidebar(
|
||||||
array(
|
array(
|
||||||
'name' => esc_html__( 'Sidebar', 'cyywordpress' ),
|
'name' => esc_html__( '页脚 Widget 1', 'cyywordpress' ),
|
||||||
'id' => 'sidebar-1',
|
'id' => 'sidebar-footer-1',
|
||||||
'description' => esc_html__( 'Add widgets here.', 'cyywordpress' ),
|
'description' => esc_html__( '页脚第一列 Widget 区域。', 'cyywordpress' ),
|
||||||
|
'before_widget' => '<div id="%1$s" class="widget %2$s">',
|
||||||
|
'after_widget' => '</div>',
|
||||||
|
'before_title' => '<h3 class="footer-widget-title">',
|
||||||
|
'after_title' => '</h3>',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
'before_widget' => '<div class="column is-3 %2$s" id="%1$s" >',
|
register_sidebar(
|
||||||
'after_widget' => '</div>',
|
array(
|
||||||
'before_title' => '<h2 class="widget-title">',
|
'name' => esc_html__( '页脚 Widget 2', 'cyywordpress' ),
|
||||||
'after_title' => '</h2>',
|
'id' => 'sidebar-footer-2',
|
||||||
)
|
'description' => esc_html__( '页脚第二列 Widget 区域。', 'cyywordpress' ),
|
||||||
);
|
'before_widget' => '<div id="%1$s" class="widget %2$s">',
|
||||||
|
'after_widget' => '</div>',
|
||||||
|
'before_title' => '<h3 class="footer-widget-title">',
|
||||||
|
'after_title' => '</h3>',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
register_sidebar(
|
||||||
|
array(
|
||||||
|
'name' => esc_html__( '页脚 Widget 3', 'cyywordpress' ),
|
||||||
|
'id' => 'sidebar-footer-3',
|
||||||
|
'description' => esc_html__( '页脚第三列 Widget 区域。', 'cyywordpress' ),
|
||||||
|
'before_widget' => '<div id="%1$s" class="widget %2$s">',
|
||||||
|
'after_widget' => '</div>',
|
||||||
|
'before_title' => '<h3 class="footer-widget-title">',
|
||||||
|
'after_title' => '</h3>',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
register_sidebar(
|
||||||
|
array(
|
||||||
|
'name' => esc_html__( '导航栏右侧', 'cyywordpress' ),
|
||||||
|
'id' => 'sidebar-header',
|
||||||
|
'description' => esc_html__( '出现在导航栏右侧的 Widget 区域(搜索等)。', 'cyywordpress' ),
|
||||||
|
'before_widget' => '<div id="%1$s" class="widget navbar-item %2$s">',
|
||||||
|
'after_widget' => '</div>',
|
||||||
|
'before_title' => '<span class="screen-reader-text">',
|
||||||
|
'after_title' => '</span>',
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
add_action( 'widgets_init', 'cyywordpress_widgets_init' );
|
add_action( 'widgets_init', 'cyywordpress_widgets_init' );
|
||||||
|
|
||||||
/**
|
// ---------------------------------------------------------------------------
|
||||||
* Enqueue scripts and styles.
|
// Initialise asset, SEO and schema classes
|
||||||
*/
|
// ---------------------------------------------------------------------------
|
||||||
function cyywordpress_scripts() {
|
add_action(
|
||||||
wp_enqueue_style( 'cyywordpress-style', get_stylesheet_uri(), array(), _S_VERSION );
|
'after_setup_theme',
|
||||||
wp_style_add_data( 'cyywordpress-style', 'rtl', 'replace' );
|
function (): void {
|
||||||
|
CyyWordpress_Assets::init();
|
||||||
wp_enqueue_script( 'cyywordpress-navigation', get_template_directory_uri() . '/js/navigation.js', array(), _S_VERSION, true );
|
CyyWordpress_SEO::init();
|
||||||
|
CyyWordpress_Schema::init();
|
||||||
if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
|
|
||||||
wp_enqueue_script( 'comment-reply' );
|
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
add_action( 'wp_enqueue_scripts', 'cyywordpress_scripts' );
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Lazy-load attributes on all attachment images
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
add_filter(
|
||||||
|
'wp_get_attachment_image_attributes',
|
||||||
|
function ( array $attr ): array {
|
||||||
|
$attr['loading'] = 'lazy';
|
||||||
|
$attr['decoding'] = 'async';
|
||||||
|
return $attr;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Remove WordPress emoji CDN (performance)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
|
||||||
|
remove_action( 'wp_print_styles', 'print_emoji_styles' );
|
||||||
|
remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
|
||||||
|
remove_action( 'admin_print_styles', 'print_emoji_styles' );
|
||||||
|
|
||||||
/**
|
|
||||||
* Implement the Custom Header feature.
|
|
||||||
*/
|
|
||||||
require get_template_directory() . '/inc/custom-header.php';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom template tags for this theme.
|
|
||||||
*/
|
|
||||||
require get_template_directory() . '/inc/template-tags.php';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Functions which enhance the theme by hooking into WordPress.
|
|
||||||
*/
|
|
||||||
require get_template_directory() . '/inc/template-functions.php';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Customizer additions.
|
|
||||||
*/
|
|
||||||
require get_template_directory() . '/inc/customizer.php';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load Jetpack compatibility file.
|
|
||||||
*/
|
|
||||||
if ( defined( 'JETPACK__VERSION' ) ) {
|
|
||||||
require get_template_directory() . '/inc/jetpack.php';
|
|
||||||
}
|
|
||||||
|
|
||||||
require get_template_directory() . '/inc/bulmapress_navwalker.php';
|
|
||||||
require get_template_directory() . '/inc/search.php';
|
|
||||||
require get_template_directory() . '/inc/pagination.php';
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+124
-69
@@ -2,7 +2,8 @@
|
|||||||
/**
|
/**
|
||||||
* The header for our theme
|
* The header for our theme
|
||||||
*
|
*
|
||||||
* This is the template that displays all of the <head> section and everything up until <div id="content">
|
* Displays the <head>, skip-link, navbar with custom logo, primary nav,
|
||||||
|
* search widget, dark-mode toggle, and back-to-top button.
|
||||||
*
|
*
|
||||||
* @link https://developer.wordpress.org/themes/basics/template-files/#template-partials
|
* @link https://developer.wordpress.org/themes/basics/template-files/#template-partials
|
||||||
*
|
*
|
||||||
@@ -16,83 +17,137 @@
|
|||||||
<meta charset="<?php bloginfo( 'charset' ); ?>">
|
<meta charset="<?php bloginfo( 'charset' ); ?>">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="profile" href="https://gmpg.org/xfn/11">
|
<link rel="profile" href="https://gmpg.org/xfn/11">
|
||||||
<link crossorigin="anonymous" integrity="sha512-6eqPOYQmUqGh2hFSAKha1dbQlMq1OaxityCVG80dkvGmmLdsAdrAUkgBKDV4fpAT/xOUdkB6uupudSbCwyoJPQ==" href="https://lib.baomitu.com/bulma/0.9.1/css/bulma.css" rel="stylesheet">
|
<?php wp_head(); ?>
|
||||||
<link crossorigin="anonymous" integrity="sha512-+4zCK9k+qNFUR5X+cKL9EIR+ZOhtIloNl9GIKS57V1MyNsYpYcUrUeQc9vNfzsWfV28IaLL3i96P9sdNyeRssA==" href="https://lib.baomitu.com/font-awesome/5.15.1/css/all.min.css" rel="stylesheet">
|
|
||||||
|
|
||||||
<?php wp_head(); ?>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body <?php body_class(); ?>>
|
<body <?php body_class(); ?>>
|
||||||
<?php wp_body_open(); ?>
|
<?php wp_body_open(); ?>
|
||||||
|
|
||||||
<div id="page" class="site">
|
<div id="page" class="site">
|
||||||
<!-- <a class="skip-link screen-reader-text" href="#primary">--><?php //esc_html_e( 'Skip to content', 'cyywordpress' ); ?><!--</a>-->
|
|
||||||
<button onclick="topFunction()" id="myBtn" title="回顶部">返回顶部</button>
|
|
||||||
|
|
||||||
|
<a class="skip-link screen-reader-text" href="#primary">
|
||||||
|
<?php esc_html_e( '跳到内容', 'cyywordpress' ); ?>
|
||||||
|
</a>
|
||||||
|
|
||||||
<nav class="navbar" style="box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px 0 rgba(0, 0, 0, .06) !important;" role="navigation" aria-label="main navigation">
|
<!-- Back-to-top button (hidden until scrolled 300px) -->
|
||||||
<div class="navbar-brand">
|
<button
|
||||||
<a class="navbar-item" href="<?php echo esc_url( home_url( '/' ) ); ?>">
|
id="back-to-top"
|
||||||
<img src="https://s3.ax1x.com/2021/02/26/6SEr4g.jpg" width="28" height="28">
|
class="back-to-top"
|
||||||
</a>
|
aria-label="<?php esc_attr_e( '返回顶部', 'cyywordpress' ); ?>"
|
||||||
<p class="subtitle is-4" style=" margin-top: 10px;margin-bottom: 5px;"><a href="<?php echo esc_url( home_url( '/' ) ); ?>" rel="home"><?php bloginfo( 'name' ); ?></a></p>
|
hidden
|
||||||
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
|
>
|
||||||
<span aria-hidden="true"></span>
|
<i class="fa-solid fa-arrow-up" aria-hidden="true"></i>
|
||||||
<span aria-hidden="true"></span>
|
</button>
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="navbarBasicExample" class="navbar-menu">
|
<nav
|
||||||
|
id="site-navigation"
|
||||||
|
class="navbar is-spaced site-navbar"
|
||||||
|
role="navigation"
|
||||||
|
aria-label="<?php esc_attr_e( '主导航', 'cyywordpress' ); ?>"
|
||||||
|
>
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<?php if ( has_custom_logo() ) : ?>
|
||||||
|
<a class="navbar-item navbar-logo" href="<?php echo esc_url( home_url( '/' ) ); ?>">
|
||||||
|
<?php the_custom_logo(); ?>
|
||||||
|
</a>
|
||||||
|
<?php else : ?>
|
||||||
|
<a class="navbar-item navbar-site-name" href="<?php echo esc_url( home_url( '/' ) ); ?>" rel="home">
|
||||||
|
<span class="site-title-initial">
|
||||||
|
<?php echo esc_html( mb_substr( get_bloginfo( 'name' ), 0, 1 ) ); ?>
|
||||||
|
</span>
|
||||||
|
<span class="site-title-text"><?php bloginfo( 'name' ); ?></span>
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="navbar-burger"
|
||||||
|
id="navbar-burger"
|
||||||
|
aria-label="<?php esc_attr_e( '菜单', 'cyywordpress' ); ?>"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="navbar-menu"
|
||||||
|
>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
</div><!-- .navbar-brand -->
|
||||||
|
|
||||||
<div class="navbar-start">
|
<div id="navbar-menu" class="navbar-menu">
|
||||||
<?php
|
<div class="navbar-start">
|
||||||
wp_nav_menu( array(
|
<?php
|
||||||
'theme_location' => 'menu-1',
|
wp_nav_menu(
|
||||||
'menu_id' => 'primary-menu',
|
array(
|
||||||
'depth' => 0,
|
'theme_location' => 'menu-1',
|
||||||
'container' => 'div id="navigation"',
|
'menu_id' => 'primary-menu',
|
||||||
'menu_class' => 'navbar-end',
|
'depth' => 3,
|
||||||
'fallback_cb' => 'bulmapress_navwalker::fallback',
|
'container' => false,
|
||||||
'walker' => new bulmapress_navwalker()
|
'menu_class' => 'navbar-nav-list',
|
||||||
)
|
'fallback_cb' => array( 'Bulmapress_Navwalker', 'fallback' ),
|
||||||
);
|
'walker' => new Bulmapress_Navwalker(),
|
||||||
?>
|
)
|
||||||
<div class="navbar-item has-dropdown is-hoverable">
|
);
|
||||||
<a class="navbar-link">
|
?>
|
||||||
更多
|
</div><!-- .navbar-start -->
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-end">
|
||||||
<a class="navbar-item" href="https://kaifa.baidu.com/" target="_blank">
|
<!-- Expandable search -->
|
||||||
开发者搜索(Baidu)
|
<div class="navbar-item navbar-search-wrap">
|
||||||
</a>
|
<button
|
||||||
<a class="navbar-item" href="https://learnku.com/" target="_blank">
|
class="navbar-search-toggle"
|
||||||
编程者论坛(LearnKu)
|
aria-label="<?php esc_attr_e( '搜索', 'cyywordpress' ); ?>"
|
||||||
</a>
|
aria-expanded="false"
|
||||||
<a class="navbar-item" href="http://www.oschina.net/" target="_blank">
|
aria-controls="navbar-search-form"
|
||||||
程序员资讯(osChina)
|
>
|
||||||
</a>
|
<i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
|
||||||
<hr class="navbar-divider">
|
</button>
|
||||||
<a class="navbar-item" href="http://yaoyuan.io" target="_blank">
|
<form
|
||||||
更多(chuyaoyuan)
|
id="navbar-search-form"
|
||||||
</a>
|
class="navbar-search-form"
|
||||||
</div>
|
role="search"
|
||||||
</div>
|
method="get"
|
||||||
</div>
|
action="<?php echo esc_url( home_url( '/' ) ); ?>"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
<label class="screen-reader-text" for="navbar-search-input">
|
||||||
|
<?php esc_html_e( '搜索', 'cyywordpress' ); ?>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="navbar-search-input"
|
||||||
|
class="input is-small"
|
||||||
|
type="search"
|
||||||
|
name="s"
|
||||||
|
placeholder="<?php esc_attr_e( '搜索…', 'cyywordpress' ); ?>"
|
||||||
|
value="<?php echo esc_attr( get_search_query() ); ?>"
|
||||||
|
>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dark-mode toggle -->
|
||||||
|
<div class="navbar-item">
|
||||||
|
<button
|
||||||
|
id="theme-toggle"
|
||||||
|
class="theme-toggle"
|
||||||
|
aria-label="<?php esc_attr_e( '切换深色模式', 'cyywordpress' ); ?>"
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-moon theme-toggle__dark-icon" aria-hidden="true"></i>
|
||||||
|
<i class="fa-solid fa-sun theme-toggle__light-icon" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ( is_active_sidebar( 'sidebar-header' ) ) : ?>
|
||||||
|
<?php dynamic_sidebar( 'sidebar-header' ); ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ( is_user_logged_in() ) : ?>
|
||||||
|
<div class="navbar-item">
|
||||||
|
<a class="button is-link is-light is-small" href="<?php echo esc_url( admin_url() ); ?>">
|
||||||
|
<strong><?php esc_html_e( '后台', 'cyywordpress' ); ?></strong>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div><!-- .navbar-end -->
|
||||||
|
</div><!-- #navbar-menu -->
|
||||||
|
</nav><!-- #site-navigation -->
|
||||||
|
|
||||||
|
<div id="content" class="site-content container">
|
||||||
|
|
||||||
<div class="navbar-end">
|
|
||||||
<div class="navbar-item">
|
|
||||||
<div class="buttons">
|
|
||||||
<a class="button is-link is-light" href="http://chuyaoyuan.com/wp-admin/">
|
|
||||||
<strong>后台登陆</strong>
|
|
||||||
</a>
|
|
||||||
<!-- <a class="button is-light">
|
|
||||||
退出
|
|
||||||
</a>-->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
<div class="container">
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Register block patterns for CyyWordpress.
|
||||||
|
*
|
||||||
|
* @package CyyWordpress
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ( ! function_exists( 'cyywordpress_register_block_patterns' ) ) :
|
||||||
|
/**
|
||||||
|
* Register all theme block patterns.
|
||||||
|
*/
|
||||||
|
function cyywordpress_register_block_patterns(): void {
|
||||||
|
// Register pattern category.
|
||||||
|
register_block_pattern_category(
|
||||||
|
'cyywordpress',
|
||||||
|
array( 'label' => esc_html__( 'CyyWordpress', 'cyywordpress' ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
// Load pattern files.
|
||||||
|
$pattern_files = array(
|
||||||
|
'hero-banner',
|
||||||
|
'post-card-grid',
|
||||||
|
'cta-section',
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ( $pattern_files as $pattern ) {
|
||||||
|
$file = get_template_directory() . "/patterns/{$pattern}.php";
|
||||||
|
if ( file_exists( $file ) ) {
|
||||||
|
require $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endif;
|
||||||
|
add_action( 'init', 'cyywordpress_register_block_patterns' );
|
||||||
@@ -1,61 +1,102 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Bulma-Navwalker
|
* Bulma Navwalker – secure, PHP 8.1 compatible rewrite
|
||||||
*
|
*
|
||||||
* @package Bulma-Navwalker
|
* @package CyyWordpress
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
if ( ! class_exists( 'Bulmapress_Navwalker' ) ) :
|
||||||
* Class Name: Navwalker
|
|
||||||
* Plugin Name: Bulma Navwalker
|
|
||||||
* Plugin URI: https://github.com/Poruno/Bulma-Navwalker
|
|
||||||
* Description: An extended Wordpress Navwalker object that displays Bulma framework's Navbar https://bulma.io/ in Wordpress.
|
|
||||||
* Author: Carlo Operio - https://www.linkedin.com/in/carlooperio/, Bulma-Framework
|
|
||||||
* Author URI: https://github.com/wp-bootstrap
|
|
||||||
* License: GPL-3.0+
|
|
||||||
* License URI: https://github.com/Poruno/Bulma-Navwalker/blob/master/LICENSE
|
|
||||||
*/
|
|
||||||
|
|
||||||
class bulmapress_navwalker extends Walker_Nav_Menu {
|
/**
|
||||||
|
* Custom Bulma nav-walker for WordPress menus.
|
||||||
|
*/
|
||||||
|
class Bulmapress_Navwalker extends Walker_Nav_Menu {
|
||||||
|
|
||||||
public function start_lvl( &$output, $depth = 0, $args = array() ) {
|
/**
|
||||||
|
* Opens a dropdown container.
|
||||||
$output .= "<div class='navbar-dropdown'>";
|
*
|
||||||
}
|
* @param string $output Used to append additional content (passed by reference).
|
||||||
|
* @param int $depth Depth of menu item.
|
||||||
public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
|
* @param stdClass|null $args An object of wp_nav_menu() arguments.
|
||||||
|
*/
|
||||||
$liClasses = 'navbar-item '.$item->title;
|
public function start_lvl( string &$output, int $depth = 0, ?stdClass $args = null ): void {
|
||||||
|
$output .= '<div class="navbar-dropdown">';
|
||||||
$hasChildren = $args->walker->has_children;
|
|
||||||
$liClasses .= $hasChildren? " has-dropdown is-hoverable": "";
|
|
||||||
|
|
||||||
if($hasChildren){
|
|
||||||
$output .= "<div class='".$liClasses."'>";
|
|
||||||
$output .= "\n<a class='navbar-link' href='".$item->url."'>".$item->title."</a>";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$output .= "<a class='".$liClasses."' href='".$item->url."'>".$item->title;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds has_children class to the item so end_el can determine if the current element has children
|
/**
|
||||||
if ( $hasChildren ) {
|
* Opens a menu item element and its link.
|
||||||
$item->classes[] = 'has_children';
|
*
|
||||||
|
* @param string $output Used to append additional content (passed by reference).
|
||||||
|
* @param WP_Post $item Menu item data object.
|
||||||
|
* @param int $depth Depth of menu item.
|
||||||
|
* @param stdClass|null $args An object of wp_nav_menu() arguments.
|
||||||
|
* @param int $id Current item ID.
|
||||||
|
*/
|
||||||
|
public function start_el( string &$output, \WP_Post $item, int $depth = 0, ?stdClass $args = null, int $id = 0 ): void {
|
||||||
|
$has_children = isset( $args->walker ) && $args->walker->has_children;
|
||||||
|
|
||||||
|
if ( $has_children ) {
|
||||||
|
// Mark the item so end_el knows it needs a </div> instead of </a>.
|
||||||
|
$item->classes[] = 'has_children';
|
||||||
|
|
||||||
|
$output .= '<div class="navbar-item has-dropdown is-hoverable">';
|
||||||
|
$output .= '<a class="navbar-link" href="' . esc_url( $item->url ) . '">'
|
||||||
|
. esc_html( $item->title )
|
||||||
|
. '</a>';
|
||||||
|
} else {
|
||||||
|
$output .= '<a class="navbar-item" href="' . esc_url( $item->url ) . '">'
|
||||||
|
. esc_html( $item->title );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes a menu item element.
|
||||||
|
*
|
||||||
|
* @param string $output Used to append additional content (passed by reference).
|
||||||
|
* @param WP_Post $item Menu item data object.
|
||||||
|
* @param int $depth Depth of menu item.
|
||||||
|
* @param stdClass|null $args An object of wp_nav_menu() arguments.
|
||||||
|
*/
|
||||||
|
public function end_el( string &$output, \WP_Post $item, int $depth = 0, ?stdClass $args = null ): void {
|
||||||
|
if ( in_array( 'has_children', (array) $item->classes, true ) ) {
|
||||||
|
// Dropdown wrapper opened with <div>; close the div (inner <a> was already closed).
|
||||||
|
$output .= '</div>';
|
||||||
|
} else {
|
||||||
|
// Regular item opened with <a>; close it.
|
||||||
|
$output .= '</a>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes a dropdown container.
|
||||||
|
*
|
||||||
|
* @param string $output Used to append additional content (passed by reference).
|
||||||
|
* @param int $depth Depth of menu item.
|
||||||
|
* @param stdClass|null $args An object of wp_nav_menu() arguments.
|
||||||
|
*/
|
||||||
|
public function end_lvl( string &$output, int $depth = 0, ?stdClass $args = null ): void {
|
||||||
|
$output .= '</div>'; // .navbar-dropdown
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fallback: output a link to the home page when no menu is assigned.
|
||||||
|
*
|
||||||
|
* @param array $args Arguments from wp_nav_menu().
|
||||||
|
*/
|
||||||
|
public static function fallback( array $args ): void {
|
||||||
|
if ( current_user_can( 'manage_options' ) ) {
|
||||||
|
echo '<a class="navbar-item" href="' . esc_url( admin_url( 'nav-menus.php' ) ) . '">'
|
||||||
|
. esc_html__( '请添加菜单', 'cyywordpress' )
|
||||||
|
. '</a>';
|
||||||
|
} else {
|
||||||
|
echo '<a class="navbar-item" href="' . esc_url( home_url( '/' ) ) . '">'
|
||||||
|
. esc_html__( '首页', 'cyywordpress' )
|
||||||
|
. '</a>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function end_el(&$output, $item, $depth = 0, $args = array(), $id = 0 ){
|
|
||||||
|
|
||||||
if(in_array("has_children", $item->classes)) {
|
// Back-compat alias so existing wp_nav_menu() calls still work.
|
||||||
|
class_alias( 'Bulmapress_Navwalker', 'bulmapress_navwalker' );
|
||||||
|
|
||||||
$output .= "</div>";
|
endif;
|
||||||
}
|
|
||||||
$output .= "</a>";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function end_lvl (&$output, $depth = 0, $args = array()) {
|
|
||||||
|
|
||||||
$output .= "</div>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Asset registration and enqueueing class.
|
||||||
|
*
|
||||||
|
* Reads the Vite manifest (assets/.vite/manifest.json) in production
|
||||||
|
* and falls back to direct paths in development mode.
|
||||||
|
*
|
||||||
|
* @package CyyWordpress
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ( ! class_exists( 'CyyWordpress_Assets' ) ) :
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles all script / style enqueueing for the theme.
|
||||||
|
*/
|
||||||
|
class CyyWordpress_Assets {
|
||||||
|
|
||||||
|
/** Absolute path to the compiled assets directory. */
|
||||||
|
private const ASSETS_DIR = __DIR__ . '/../assets';
|
||||||
|
|
||||||
|
/** URL to the compiled assets directory. */
|
||||||
|
private static string $assets_url = '';
|
||||||
|
|
||||||
|
/** Parsed Vite manifest. */
|
||||||
|
private static array $manifest = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers hooks.
|
||||||
|
*/
|
||||||
|
public static function init(): void {
|
||||||
|
self::$assets_url = get_template_directory_uri() . '/assets';
|
||||||
|
self::load_manifest();
|
||||||
|
|
||||||
|
add_action( 'wp_enqueue_scripts', array( static::class, 'enqueue_frontend' ) );
|
||||||
|
add_action( 'admin_enqueue_scripts', array( static::class, 'enqueue_admin' ) );
|
||||||
|
add_filter( 'script_loader_tag', array( static::class, 'add_defer_attr' ), 10, 2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads and caches the Vite manifest if it exists.
|
||||||
|
*/
|
||||||
|
private static function load_manifest(): void {
|
||||||
|
$manifest_path = self::ASSETS_DIR . '/.vite/manifest.json';
|
||||||
|
|
||||||
|
if ( file_exists( $manifest_path ) ) {
|
||||||
|
$json = file_get_contents( $manifest_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions
|
||||||
|
if ( $json ) {
|
||||||
|
self::$manifest = json_decode( $json, true ) ?? [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the hashed asset URL from the Vite manifest
|
||||||
|
* or falls back to the raw path.
|
||||||
|
*
|
||||||
|
* @param string $entry The entry key (e.g. "src/ts/main.ts").
|
||||||
|
* @param string $type 'js' or 'css'.
|
||||||
|
*/
|
||||||
|
private static function asset_url( string $entry, string $type = 'js' ): string {
|
||||||
|
if ( ! empty( self::$manifest[ $entry ][ 'file' ] ) ) {
|
||||||
|
return self::$assets_url . '/' . self::$manifest[ $entry ][ 'file' ];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dev fallback.
|
||||||
|
return self::$assets_url . "/$type/" . basename( $entry, '.ts' ) . '.' . $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the filemtime-based version string for a local file,
|
||||||
|
* used for cache-busting in development.
|
||||||
|
*
|
||||||
|
* @param string $path Relative path from assets dir.
|
||||||
|
*/
|
||||||
|
private static function file_version( string $path ): string {
|
||||||
|
$abs = self::ASSETS_DIR . '/' . ltrim( $path, '/' );
|
||||||
|
return file_exists( $abs ) ? (string) filemtime( $abs ) : _S_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueue front-end styles and scripts.
|
||||||
|
*/
|
||||||
|
public static function enqueue_frontend(): void {
|
||||||
|
// Main CSS.
|
||||||
|
$css_url = self::asset_url( 'src/ts/main.ts', 'css' );
|
||||||
|
wp_enqueue_style(
|
||||||
|
'cyywordpress-main',
|
||||||
|
$css_url,
|
||||||
|
array(),
|
||||||
|
_S_VERSION
|
||||||
|
);
|
||||||
|
wp_style_add_data( 'cyywordpress-main', 'rtl', 'replace' );
|
||||||
|
|
||||||
|
// Legacy style.css (theme header required by WP).
|
||||||
|
wp_enqueue_style(
|
||||||
|
'cyywordpress-style',
|
||||||
|
get_stylesheet_uri(),
|
||||||
|
array( 'cyywordpress-main' ),
|
||||||
|
_S_VERSION
|
||||||
|
);
|
||||||
|
|
||||||
|
// Main JS.
|
||||||
|
$js_url = self::asset_url( 'src/ts/main.ts', 'js' );
|
||||||
|
wp_enqueue_script(
|
||||||
|
'cyywordpress-main',
|
||||||
|
$js_url,
|
||||||
|
array(),
|
||||||
|
_S_VERSION,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
// Font Awesome (local).
|
||||||
|
wp_enqueue_style(
|
||||||
|
'font-awesome',
|
||||||
|
get_template_directory_uri() . '/node_modules/@fortawesome/fontawesome-free/css/all.min.css',
|
||||||
|
array(),
|
||||||
|
'6.5.2'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Comment reply: only when needed.
|
||||||
|
if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
|
||||||
|
wp_enqueue_script( 'comment-reply' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueue admin / Customizer scripts.
|
||||||
|
*/
|
||||||
|
public static function enqueue_admin(): void {
|
||||||
|
// Customizer preview script is handled via customize_preview_init hook.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add `defer` attribute to all theme JS files.
|
||||||
|
*
|
||||||
|
* @param string $tag The <script> HTML tag.
|
||||||
|
* @param string $handle The registered script handle.
|
||||||
|
*/
|
||||||
|
public static function add_defer_attr( string $tag, string $handle ): string {
|
||||||
|
$defer_handles = array( 'cyywordpress-main' );
|
||||||
|
|
||||||
|
if ( in_array( $handle, $defer_handles, true ) ) {
|
||||||
|
return str_replace( ' src=', ' defer src=', $tag );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endif;
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* JSON-LD Schema.org structured data class.
|
||||||
|
*
|
||||||
|
* Outputs inline <script type="application/ld+json"> on relevant pages.
|
||||||
|
* Skips output when a dedicated SEO plugin is active.
|
||||||
|
*
|
||||||
|
* @package CyyWordpress
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ( ! class_exists( 'CyyWordpress_Schema' ) ) :
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles JSON-LD output.
|
||||||
|
*/
|
||||||
|
class CyyWordpress_Schema {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register hooks.
|
||||||
|
*/
|
||||||
|
public static function init(): void {
|
||||||
|
// Bail early if a popular SEO plugin already handles schema.
|
||||||
|
if (
|
||||||
|
defined( 'WPSEO_VERSION' )
|
||||||
|
|| defined( 'RANKMATH_VERSION' )
|
||||||
|
|| class_exists( 'All_in_One_SEO_Pack' )
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_action( 'wp_head', array( static::class, 'output_schema' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine page type and output the correct schema.
|
||||||
|
*/
|
||||||
|
public static function output_schema(): void {
|
||||||
|
$schema = null;
|
||||||
|
|
||||||
|
if ( is_front_page() || is_home() ) {
|
||||||
|
$schema = self::website_schema();
|
||||||
|
} elseif ( is_singular( 'post' ) ) {
|
||||||
|
$schema = self::article_schema();
|
||||||
|
} elseif ( is_author() ) {
|
||||||
|
$schema = self::person_schema();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $schema ) {
|
||||||
|
// json_encode with JSON_UNESCAPED_UNICODE for readable Chinese output.
|
||||||
|
echo '<script type="application/ld+json">'
|
||||||
|
. wp_json_encode( $schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT )
|
||||||
|
. '</script>' . PHP_EOL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSite schema with SearchAction for Sitelink Search Box.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
private static function website_schema(): array {
|
||||||
|
return array(
|
||||||
|
'@context' => 'https://schema.org',
|
||||||
|
'@type' => 'WebSite',
|
||||||
|
'name' => get_bloginfo( 'name' ),
|
||||||
|
'url' => home_url( '/' ),
|
||||||
|
'potentialAction' => array(
|
||||||
|
'@type' => 'SearchAction',
|
||||||
|
'target' => array(
|
||||||
|
'@type' => 'EntryPoint',
|
||||||
|
'urlTemplate' => home_url( '/?s={search_term_string}' ),
|
||||||
|
),
|
||||||
|
'query-input' => 'required name=search_term_string',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Article schema for single posts.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
private static function article_schema(): array {
|
||||||
|
global $post;
|
||||||
|
|
||||||
|
$author_id = (int) get_the_author_meta( 'ID' );
|
||||||
|
$author_name = get_the_author_meta( 'display_name', $author_id );
|
||||||
|
$author_url = get_author_posts_url( $author_id );
|
||||||
|
$image_url = '';
|
||||||
|
|
||||||
|
if ( has_post_thumbnail() ) {
|
||||||
|
$src = wp_get_attachment_image_src( (int) get_post_thumbnail_id(), 'large' );
|
||||||
|
if ( $src ) {
|
||||||
|
$image_url = $src[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$schema = array(
|
||||||
|
'@context' => 'https://schema.org',
|
||||||
|
'@type' => 'Article',
|
||||||
|
'headline' => get_the_title(),
|
||||||
|
'url' => (string) get_permalink(),
|
||||||
|
'datePublished' => get_the_date( DATE_W3C ),
|
||||||
|
'dateModified' => get_the_modified_date( DATE_W3C ),
|
||||||
|
'author' => array(
|
||||||
|
'@type' => 'Person',
|
||||||
|
'name' => $author_name,
|
||||||
|
'url' => $author_url,
|
||||||
|
),
|
||||||
|
'publisher' => array(
|
||||||
|
'@type' => 'Organization',
|
||||||
|
'name' => get_bloginfo( 'name' ),
|
||||||
|
'url' => home_url( '/' ),
|
||||||
|
),
|
||||||
|
'description' => has_excerpt()
|
||||||
|
? get_the_excerpt()
|
||||||
|
: wp_trim_words( wp_strip_all_tags( $post->post_content ), 30 ),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( $image_url ) {
|
||||||
|
$schema['image'] = $image_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Person schema for author archive pages.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
private static function person_schema(): array {
|
||||||
|
$author = get_queried_object();
|
||||||
|
$author_id = ( $author instanceof WP_User ) ? $author->ID : 0;
|
||||||
|
$author_name = get_the_author_meta( 'display_name', $author_id );
|
||||||
|
$author_url = get_author_posts_url( $author_id );
|
||||||
|
$bio = get_the_author_meta( 'description', $author_id );
|
||||||
|
|
||||||
|
$schema = array(
|
||||||
|
'@context' => 'https://schema.org',
|
||||||
|
'@type' => 'Person',
|
||||||
|
'name' => $author_name,
|
||||||
|
'url' => $author_url,
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( $bio ) {
|
||||||
|
$schema['description'] = $bio;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $schema;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endif;
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* SEO / Meta-tags class.
|
||||||
|
*
|
||||||
|
* Outputs Open Graph and Twitter Card meta tags.
|
||||||
|
* Does NOT run if a dedicated SEO plugin (Yoast, RankMath, AIOSEO) is active.
|
||||||
|
*
|
||||||
|
* @package CyyWordpress
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ( ! class_exists( 'CyyWordpress_SEO' ) ) :
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages Open Graph + Twitter Card meta output.
|
||||||
|
*/
|
||||||
|
class CyyWordpress_SEO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register hooks.
|
||||||
|
*/
|
||||||
|
public static function init(): void {
|
||||||
|
// Bail early if a popular SEO plugin is active.
|
||||||
|
if (
|
||||||
|
defined( 'WPSEO_VERSION' ) // Yoast SEO
|
||||||
|
|| defined( 'RANKMATH_VERSION' ) // RankMath
|
||||||
|
|| class_exists( 'All_in_One_SEO_Pack' )
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_action( 'wp_head', array( static::class, 'output_meta_tags' ), 5 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output all meta tags.
|
||||||
|
*/
|
||||||
|
public static function output_meta_tags(): void {
|
||||||
|
$description = self::get_description();
|
||||||
|
$image = self::get_image();
|
||||||
|
$type = is_singular() ? 'article' : 'website';
|
||||||
|
$url = esc_url( self::get_canonical_url() );
|
||||||
|
$title = esc_attr( wp_get_document_title() );
|
||||||
|
|
||||||
|
echo "\n<!-- CyyWordpress SEO Meta -->\n";
|
||||||
|
|
||||||
|
// Open Graph.
|
||||||
|
printf( '<meta property="og:title" content="%s">' . PHP_EOL, esc_attr( $title ) );
|
||||||
|
printf( '<meta property="og:description" content="%s">' . PHP_EOL, esc_attr( $description ) );
|
||||||
|
printf( '<meta property="og:url" content="%s">' . PHP_EOL, $url );
|
||||||
|
printf( '<meta property="og:type" content="%s">' . PHP_EOL, esc_attr( $type ) );
|
||||||
|
printf( '<meta property="og:site_name" content="%s">' . PHP_EOL, esc_attr( get_bloginfo( 'name' ) ) );
|
||||||
|
|
||||||
|
if ( $image ) {
|
||||||
|
printf( '<meta property="og:image" content="%s">' . PHP_EOL, esc_url( $image ) );
|
||||||
|
printf( '<meta property="og:image:alt" content="%s">' . PHP_EOL, esc_attr( $title ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Twitter Card.
|
||||||
|
$card_type = $image ? 'summary_large_image' : 'summary';
|
||||||
|
printf( '<meta name="twitter:card" content="%s">' . PHP_EOL, esc_attr( $card_type ) );
|
||||||
|
printf( '<meta name="twitter:title" content="%s">' . PHP_EOL, esc_attr( $title ) );
|
||||||
|
printf( '<meta name="twitter:description" content="%s">' . PHP_EOL, esc_attr( $description ) );
|
||||||
|
|
||||||
|
if ( $image ) {
|
||||||
|
printf( '<meta name="twitter:image" content="%s">' . PHP_EOL, esc_url( $image ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "<!-- /CyyWordpress SEO Meta -->\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a cleaned excerpt / description.
|
||||||
|
*/
|
||||||
|
private static function get_description(): string {
|
||||||
|
if ( is_singular() ) {
|
||||||
|
global $post;
|
||||||
|
$excerpt = has_excerpt() ? get_the_excerpt() : wp_trim_words( wp_strip_all_tags( $post->post_content ), 30 );
|
||||||
|
return sanitize_text_field( $excerpt );
|
||||||
|
}
|
||||||
|
|
||||||
|
$tagline = get_bloginfo( 'description' );
|
||||||
|
return sanitize_text_field( $tagline );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the OG image URL.
|
||||||
|
*/
|
||||||
|
private static function get_image(): string {
|
||||||
|
if ( is_singular() && has_post_thumbnail() ) {
|
||||||
|
$src = wp_get_attachment_image_src( (int) get_post_thumbnail_id(), 'large' );
|
||||||
|
if ( $src ) {
|
||||||
|
return $src[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Site-wide fallback: custom logo.
|
||||||
|
$logo_id = get_theme_mod( 'custom_logo' );
|
||||||
|
if ( $logo_id ) {
|
||||||
|
$src = wp_get_attachment_image_src( (int) $logo_id, 'full' );
|
||||||
|
if ( $src ) {
|
||||||
|
return $src[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the canonical URL for the current page.
|
||||||
|
*/
|
||||||
|
private static function get_canonical_url(): string {
|
||||||
|
if ( is_singular() ) {
|
||||||
|
return (string) get_permalink();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( is_home() ) {
|
||||||
|
return home_url( '/' );
|
||||||
|
}
|
||||||
|
|
||||||
|
return home_url( add_query_arg( array() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endif;
|
||||||
+63
-12
@@ -6,13 +6,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add postMessage support for site title and description for the Theme Customizer.
|
* Add postMessage support and custom options for the Theme Customizer.
|
||||||
*
|
*
|
||||||
* @param WP_Customize_Manager $wp_customize Theme Customizer object.
|
* @param WP_Customize_Manager $wp_customize Theme Customizer object.
|
||||||
*/
|
*/
|
||||||
function cyywordpress_customize_register( $wp_customize ) {
|
function cyywordpress_customize_register( WP_Customize_Manager $wp_customize ): void {
|
||||||
$wp_customize->get_setting( 'blogname' )->transport = 'postMessage';
|
$wp_customize->get_setting( 'blogname' )->transport = 'postMessage';
|
||||||
$wp_customize->get_setting( 'blogdescription' )->transport = 'postMessage';
|
$wp_customize->get_setting( 'blogdescription' )->transport = 'postMessage';
|
||||||
$wp_customize->get_setting( 'header_textcolor' )->transport = 'postMessage';
|
$wp_customize->get_setting( 'header_textcolor' )->transport = 'postMessage';
|
||||||
|
|
||||||
if ( isset( $wp_customize->selective_refresh ) ) {
|
if ( isset( $wp_customize->selective_refresh ) ) {
|
||||||
@@ -31,31 +31,82 @@ function cyywordpress_customize_register( $wp_customize ) {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Section: Front Page Banner
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
$wp_customize->add_section(
|
||||||
|
'cyywordpress_frontpage',
|
||||||
|
array(
|
||||||
|
'title' => esc_html__( '首页横幅', 'cyywordpress' ),
|
||||||
|
'priority' => 130,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$wp_customize->add_setting(
|
||||||
|
'cyywordpress_welcome_text',
|
||||||
|
array(
|
||||||
|
// 存原始字符串,输出时再转义。
|
||||||
|
'default' => __( '欢迎来到这!Hello everyone.', 'cyywordpress' ),
|
||||||
|
'sanitize_callback' => 'sanitize_text_field',
|
||||||
|
'transport' => 'postMessage',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$wp_customize->add_control(
|
||||||
|
'cyywordpress_welcome_text',
|
||||||
|
array(
|
||||||
|
'label' => esc_html__( '欢迎横幅文字', 'cyywordpress' ),
|
||||||
|
'section' => 'cyywordpress_frontpage',
|
||||||
|
'type' => 'text',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$wp_customize->add_setting(
|
||||||
|
'cyywordpress_show_welcome_banner',
|
||||||
|
array(
|
||||||
|
'default' => true,
|
||||||
|
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||||
|
'transport' => 'refresh',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$wp_customize->add_control(
|
||||||
|
'cyywordpress_show_welcome_banner',
|
||||||
|
array(
|
||||||
|
'label' => esc_html__( '显示欢迎横幅', 'cyywordpress' ),
|
||||||
|
'section' => 'cyywordpress_frontpage',
|
||||||
|
'type' => 'checkbox',
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
add_action( 'customize_register', 'cyywordpress_customize_register' );
|
add_action( 'customize_register', 'cyywordpress_customize_register' );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the site title for the selective refresh partial.
|
* Render the site title for the selective refresh partial.
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
function cyywordpress_customize_partial_blogname() {
|
function cyywordpress_customize_partial_blogname(): void {
|
||||||
bloginfo( 'name' );
|
bloginfo( 'name' );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the site tagline for the selective refresh partial.
|
* Render the site tagline for the selective refresh partial.
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
function cyywordpress_customize_partial_blogdescription() {
|
function cyywordpress_customize_partial_blogdescription(): void {
|
||||||
bloginfo( 'description' );
|
bloginfo( 'description' );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binds JS handlers to make Theme Customizer preview reload changes asynchronously.
|
* Binds JS handlers to make Theme Customizer preview reload changes asynchronously.
|
||||||
*/
|
*/
|
||||||
function cyywordpress_customize_preview_js() {
|
function cyywordpress_customize_preview_js(): void {
|
||||||
wp_enqueue_script( 'cyywordpress-customizer', get_template_directory_uri() . '/js/customizer.js', array( 'customize-preview' ), _S_VERSION, true );
|
wp_enqueue_script(
|
||||||
|
'cyywordpress-customizer',
|
||||||
|
get_template_directory_uri() . '/js/customizer.js',
|
||||||
|
array( 'customize-preview' ),
|
||||||
|
_S_VERSION,
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
add_action( 'customize_preview_init', 'cyywordpress_customize_preview_js' );
|
add_action( 'customize_preview_init', 'cyywordpress_customize_preview_js' );
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,8 @@
|
|||||||
/**
|
/**
|
||||||
* The main template file
|
* The main template file
|
||||||
*
|
*
|
||||||
* This is the most generic template file in a WordPress theme
|
* Responsive 1/2/3-column post grid, sticky hero for top-pinned posts,
|
||||||
* and one of the two required files for a theme (the other being style.css).
|
* customiser-driven welcome banner, and pagination.
|
||||||
* It is used to display a page when nothing more specific matches a query.
|
|
||||||
* E.g., it puts together the home page when no home.php file exists.
|
|
||||||
*
|
*
|
||||||
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/
|
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/
|
||||||
*
|
*
|
||||||
@@ -14,69 +12,89 @@
|
|||||||
|
|
||||||
get_header();
|
get_header();
|
||||||
?>
|
?>
|
||||||
<section class="section">
|
|
||||||
<div class="container">
|
|
||||||
<!-- <h1 class="title">
|
|
||||||
<?php /*bloginfo( 'name' ); */?>
|
|
||||||
</h1>-->
|
|
||||||
<p class="subtitle">
|
|
||||||
<?php bloginfo( 'description' ); ?>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<br>
|
<div id="primary" class="content-area">
|
||||||
<div class="notification is-primary">
|
<main id="main" class="site-main" role="main">
|
||||||
欢迎来到这! <strong>Hello everyone.</strong>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<nav class="breadcrumb" aria-label="breadcrumbs">
|
<?php
|
||||||
<ul>
|
// Welcome banner – controlled via Customizer.
|
||||||
<li>
|
$show_banner = get_theme_mod( 'cyywordpress_show_welcome_banner', true );
|
||||||
<a href="<?php echo esc_url( home_url( '/' ) ); ?>" >
|
$welcome_text = get_theme_mod( 'cyywordpress_welcome_text', __( '欢迎来到这!Hello everyone.', 'cyywordpress' ) );
|
||||||
<span class="icon is-small">
|
|
||||||
<i class="fas fa-home" aria-hidden="true"></i>
|
|
||||||
</span>
|
|
||||||
<span>首页</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
|
if ( $show_banner && ( is_home() || is_front_page() ) ) :
|
||||||
|
?>
|
||||||
|
<div class="notification is-primary welcome-banner" data-customizer="cyywordpress_welcome_text">
|
||||||
|
<?php echo esc_html( $welcome_text ); ?>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
endif;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<nav class="breadcrumb" aria-label="<?php esc_attr_e( '面包屑导航', 'cyywordpress' ); ?>">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="<?php echo esc_url( home_url( '/' ) ); ?>">
|
||||||
|
<span class="icon is-small"><i class="fa-solid fa-house" aria-hidden="true"></i></span>
|
||||||
|
<span><?php esc_html_e( '首页', 'cyywordpress' ); ?></span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php if ( is_home() && ! is_front_page() ) : ?>
|
||||||
|
<li class="is-active">
|
||||||
|
<a href="#" aria-current="page"><?php single_post_title(); ?></a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<?php
|
<?php if ( have_posts() ) : ?>
|
||||||
if ( have_posts() ) :
|
|
||||||
|
|
||||||
if ( is_home() && ! is_front_page() ) :
|
<?php
|
||||||
?>
|
// Separate sticky posts for hero treatment.
|
||||||
<p class="title is-4"><?php single_post_title(); ?></p>article
|
$sticky_ids = get_option( 'sticky_posts', array() );
|
||||||
<?php
|
$hero_printed = false;
|
||||||
endif;
|
?>
|
||||||
|
|
||||||
/* Start the Loop */
|
<div class="posts-grid columns is-multiline">
|
||||||
while ( have_posts() ) :
|
<?php
|
||||||
the_post();
|
while ( have_posts() ) :
|
||||||
|
the_post();
|
||||||
|
|
||||||
/*
|
// Sticky → hero card (full-width), only first sticky.
|
||||||
* Include the Post-Type-specific template for the content.
|
if ( is_sticky() && ! $hero_printed && is_home() && ! is_paged() ) :
|
||||||
* If you want to override this in a child theme, then include a file
|
$hero_printed = true;
|
||||||
* called content-___.php (where ___ is the Post Type name) and that will be used instead.
|
echo '<div class="column is-12 post-card--hero">';
|
||||||
*/
|
get_template_part( 'template-parts/content', get_post_type() );
|
||||||
get_template_part( 'template-parts/content', get_post_type() );
|
echo '</div>';
|
||||||
|
else :
|
||||||
|
echo '<div class="column is-12-mobile is-6-tablet is-4-desktop">';
|
||||||
|
get_template_part( 'template-parts/content', get_post_type() );
|
||||||
|
echo '</div>';
|
||||||
|
endif;
|
||||||
|
|
||||||
endwhile;
|
endwhile;
|
||||||
|
?>
|
||||||
|
</div><!-- .posts-grid -->
|
||||||
|
|
||||||
|
<div class="posts-pagination">
|
||||||
|
<?php the_posts_pagination(
|
||||||
|
array(
|
||||||
|
'mid_size' => 2,
|
||||||
|
'prev_text' => '<i class="fa-solid fa-chevron-left" aria-hidden="true"></i> ' . esc_html__( '上一页', 'cyywordpress' ),
|
||||||
|
'next_text' => esc_html__( '下一页', 'cyywordpress' ) . ' <i class="fa-solid fa-chevron-right" aria-hidden="true"></i>',
|
||||||
|
)
|
||||||
|
); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
the_posts_navigation();
|
<?php else : ?>
|
||||||
|
<?php get_template_part( 'template-parts/content', 'none' ); ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
else :
|
</main><!-- #main -->
|
||||||
|
</div><!-- #primary -->
|
||||||
get_template_part( 'template-parts/content', 'none' );
|
|
||||||
|
|
||||||
endif;
|
|
||||||
?>
|
|
||||||
|
|
||||||
|
<?php
|
||||||
|
get_sidebar();
|
||||||
|
get_footer();
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
|
|||||||
+32
-27
@@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "underscores",
|
"name": "cyywordpress",
|
||||||
"version": "1.0.0",
|
"version": "2.0.0",
|
||||||
"description": "Hi. I'm a starter theme called _s, or underscores, if you like. I'm a theme meant for hacking so don't use me as a Parent Theme. Instead try turning me into the next, most awesome, WordPress theme out there. That's what I'm here for.",
|
"description": "CyyWordpress – a modern WordPress theme built on Bulma 1.x and Underscores.",
|
||||||
"author": "Automattic Theme Team",
|
"author": "Chuyaoyuan",
|
||||||
"license": "GPL-2.0-or-later",
|
"license": "GPL-2.0-or-later",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"WordPress",
|
"WordPress",
|
||||||
"Theme"
|
"Theme",
|
||||||
|
"Bulma"
|
||||||
],
|
],
|
||||||
"homepage": "https://github.com/Automattic/_s#readme",
|
"homepage": "https://github.com/Automattic/_s#readme",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -17,30 +18,34 @@
|
|||||||
"url": "https://github.com/Automattic/_s/issues"
|
"url": "https://github.com/Automattic/_s/issues"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@wordpress/scripts": "^12.1.0",
|
"@fortawesome/fontawesome-free": "^6.5.2",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||||
|
"@typescript-eslint/parser": "^7.0.0",
|
||||||
|
"@wordpress/eslint-plugin": "^21.0.0",
|
||||||
|
"@wordpress/scripts": "^30.0.0",
|
||||||
|
"autoprefixer": "^10.4.19",
|
||||||
|
"bulma": "^1.0.2",
|
||||||
|
"cssnano": "^7.0.0",
|
||||||
"dir-archiver": "^1.1.1",
|
"dir-archiver": "^1.1.1",
|
||||||
"node-sass": "^4.14.1",
|
"eslint": "^8.57.0",
|
||||||
"rtlcss": "^2.5.0"
|
"postcss": "^8.4.38",
|
||||||
},
|
"rtlcss": "^4.3.0",
|
||||||
"rtlcssConfig": {
|
"sass": "^1.77.0",
|
||||||
"options": {
|
"stylelint": "^16.6.0",
|
||||||
"autoRename": false,
|
"stylelint-config-standard-scss": "^14.0.0",
|
||||||
"autoRenameStrict": false,
|
"typescript": "^5.4.5",
|
||||||
"blacklist": {},
|
"vite": "^5.2.11",
|
||||||
"clean": true,
|
"vite-plugin-sass-glob-import": "^1.0.0"
|
||||||
"greedy": false,
|
|
||||||
"processUrls": false,
|
|
||||||
"stringMap": []
|
|
||||||
},
|
|
||||||
"plugins": [],
|
|
||||||
"map": false
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"watch": "node-sass sass/ -o ./ --source-map true --output-style expanded --indent-type tab --indent-width 1 -w",
|
"dev": "vite",
|
||||||
"compile:css": "node-sass sass/ -o ./ && stylelint '*.css' --fix || true && stylelint '*.css' --fix",
|
"build": "vite build",
|
||||||
"compile:rtl": "rtlcss style.css style-rtl.css",
|
"build:rtl": "rtlcss assets/css/main.css assets/css/main-rtl.css",
|
||||||
"lint:scss": "wp-scripts lint-style 'sass/**/*.scss'",
|
"lint": "eslint 'src/**/*.{ts,js}' && stylelint 'scss/**/*.scss'",
|
||||||
"lint:js": "wp-scripts lint-js 'js/*.js'",
|
"lint:js": "wp-scripts lint-js 'src/**/*.ts'",
|
||||||
"bundle": "dir-archiver --src . --dest ../_s.zip --exclude .DS_Store .stylelintrc.json .eslintrc .git .gitattributes .github .gitignore README.md composer.json composer.lock node_modules vendor package-lock.json package.json .travis.yml phpcs.xml.dist sass style.css.map"
|
"lint:scss": "stylelint 'scss/**/*.scss'",
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"bundle": "dir-archiver --src . --dest ../cyywordpress.zip --exclude .DS_Store .stylelintrc.json .eslintrc.json .git .gitattributes .github .gitignore composer.json composer.lock node_modules vendor package-lock.json package.json phpcs.xml.dist scss src tsconfig.json vite.config.ts postcss.config.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Block Pattern: CTA Section
|
||||||
|
*
|
||||||
|
* @package CyyWordpress
|
||||||
|
*/
|
||||||
|
|
||||||
|
register_block_pattern(
|
||||||
|
'cyywordpress/cta-section',
|
||||||
|
array(
|
||||||
|
'title' => esc_html__( '行动号召区域', 'cyywordpress' ),
|
||||||
|
'description' => esc_html__( '中心对齐的行动号召区域,含标题、描述和按钮。', 'cyywordpress' ),
|
||||||
|
'categories' => array( 'cyywordpress' ),
|
||||||
|
'content' => '<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"top":"var:preset|spacing|70","bottom":"var:preset|spacing|70"}},"color":{"background":"var:preset|color|light"}},"layout":{"type":"constrained"}} -->
|
||||||
|
<div class="wp-block-group alignfull has-background" style="background-color:var(--wp--preset--color--light);padding-top:var(--wp--preset--spacing--70);padding-bottom:var(--wp--preset--spacing--70)">
|
||||||
|
<!-- wp:group {"layout":{"type":"flex","orientation":"vertical","justifyContent":"center"}} -->
|
||||||
|
<div class="wp-block-group">
|
||||||
|
<!-- wp:heading {"textAlign":"center","level":2} -->
|
||||||
|
<h2 class="wp-block-heading has-text-align-center">' . esc_html__( '准备好开始了吗?', 'cyywordpress' ) . '</h2>
|
||||||
|
<!-- /wp:heading -->
|
||||||
|
<!-- wp:paragraph {"align":"center","style":{"typography":{"fontSize":"1.125rem"}}} -->
|
||||||
|
<p class="has-text-align-center" style="font-size:1.125rem">' . esc_html__( '加入我们,探索更多精彩内容。', 'cyywordpress' ) . '</p>
|
||||||
|
<!-- /wp:paragraph -->
|
||||||
|
<!-- wp:buttons {"layout":{"type":"flex","justifyContent":"center"},"style":{"spacing":{"margin":{"top":"var:preset|spacing|40"}}}} -->
|
||||||
|
<div class="wp-block-buttons" style="margin-top:var(--wp--preset--spacing--40)">
|
||||||
|
<!-- wp:button {"backgroundColor":"primary","textColor":"white","style":{"border":{"radius":"6px"}}} -->
|
||||||
|
<div class="wp-block-button"><a class="wp-block-button__link has-primary-background-color has-white-color has-text-color has-background wp-element-button" style="border-radius:6px">' . esc_html__( '立即开始', 'cyywordpress' ) . '</a></div>
|
||||||
|
<!-- /wp:button -->
|
||||||
|
<!-- wp:button {"className":"is-style-outline","style":{"border":{"radius":"6px"}}} -->
|
||||||
|
<div class="wp-block-button is-style-outline"><a class="wp-block-button__link wp-element-button" style="border-radius:6px">' . esc_html__( '了解更多', 'cyywordpress' ) . '</a></div>
|
||||||
|
<!-- /wp:button -->
|
||||||
|
</div>
|
||||||
|
<!-- /wp:buttons -->
|
||||||
|
</div>
|
||||||
|
<!-- /wp:group -->
|
||||||
|
</div>
|
||||||
|
<!-- /wp:group -->',
|
||||||
|
)
|
||||||
|
);
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Block Pattern: Hero Banner
|
||||||
|
*
|
||||||
|
* @package CyyWordpress
|
||||||
|
*/
|
||||||
|
|
||||||
|
register_block_pattern(
|
||||||
|
'cyywordpress/hero-banner',
|
||||||
|
array(
|
||||||
|
'title' => esc_html__( 'Hero 横幅', 'cyywordpress' ),
|
||||||
|
'description' => esc_html__( '大标题 + 副标题 + 行动号召按钮的 Hero 区域。', 'cyywordpress' ),
|
||||||
|
'categories' => array( 'cyywordpress' ),
|
||||||
|
'content' => '<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"top":"var:preset|spacing|70","bottom":"var:preset|spacing|70"}},"color":{"gradient":"linear-gradient(135deg,#00d1b2 0%,#3e8ed0 100%)"}},"layout":{"type":"constrained"}} -->
|
||||||
|
<div class="wp-block-group alignfull has-background" style="padding-top:var(--wp--preset--spacing--70);padding-bottom:var(--wp--preset--spacing--70)">
|
||||||
|
<!-- wp:group {"layout":{"type":"flex","orientation":"vertical","justifyContent":"center"}} -->
|
||||||
|
<div class="wp-block-group">
|
||||||
|
<!-- wp:heading {"textAlign":"center","level":1,"style":{"color":{"text":"#ffffff"},"typography":{"fontSize":"clamp(2rem,5vw,3.5rem)","fontWeight":"700"}}} -->
|
||||||
|
<h1 class="wp-block-heading has-text-align-center" style="color:#ffffff;font-size:clamp(2rem,5vw,3.5rem);font-weight:700">' . esc_html__( '欢迎来到我的博客', 'cyywordpress' ) . '</h1>
|
||||||
|
<!-- /wp:heading -->
|
||||||
|
<!-- wp:paragraph {"align":"center","style":{"color":{"text":"rgba(255,255,255,0.9)"},"typography":{"fontSize":"1.25rem"}}} -->
|
||||||
|
<p class="has-text-align-center" style="color:rgba(255,255,255,0.9);font-size:1.25rem">' . esc_html__( '分享技术、生活与思考', 'cyywordpress' ) . '</p>
|
||||||
|
<!-- /wp:paragraph -->
|
||||||
|
<!-- wp:buttons {"layout":{"type":"flex","justifyContent":"center"},"style":{"spacing":{"margin":{"top":"var:preset|spacing|40"}}}} -->
|
||||||
|
<div class="wp-block-buttons" style="margin-top:var(--wp--preset--spacing--40)">
|
||||||
|
<!-- wp:button {"backgroundColor":"white","textColor":"primary","style":{"border":{"radius":"6px"}}} -->
|
||||||
|
<div class="wp-block-button"><a class="wp-block-button__link has-white-background-color has-primary-color has-text-color has-background wp-element-button" style="border-radius:6px">' . esc_html__( '浏览文章', 'cyywordpress' ) . '</a></div>
|
||||||
|
<!-- /wp:button -->
|
||||||
|
</div>
|
||||||
|
<!-- /wp:buttons -->
|
||||||
|
</div>
|
||||||
|
<!-- /wp:group -->
|
||||||
|
</div>
|
||||||
|
<!-- /wp:group -->',
|
||||||
|
)
|
||||||
|
);
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Block Pattern: Post Card Grid
|
||||||
|
*
|
||||||
|
* @package CyyWordpress
|
||||||
|
*/
|
||||||
|
|
||||||
|
register_block_pattern(
|
||||||
|
'cyywordpress/post-card-grid',
|
||||||
|
array(
|
||||||
|
'title' => esc_html__( '文章卡片网格', 'cyywordpress' ),
|
||||||
|
'description' => esc_html__( '响应式三列文章卡片网格布局。', 'cyywordpress' ),
|
||||||
|
'categories' => array( 'cyywordpress' ),
|
||||||
|
'content' => '<!-- wp:group {"align":"wide","layout":{"type":"constrained"}} -->
|
||||||
|
<div class="wp-block-group alignwide">
|
||||||
|
<!-- wp:heading {"textAlign":"center","style":{"spacing":{"margin":{"bottom":"var:preset|spacing|50"}}}} -->
|
||||||
|
<h2 class="wp-block-heading has-text-align-center">' . esc_html__( '最新文章', 'cyywordpress' ) . '</h2>
|
||||||
|
<!-- /wp:heading -->
|
||||||
|
<!-- wp:columns {"isStackedOnMobile":true,"style":{"spacing":{"blockGap":{"top":"var:preset|spacing|40","left":"var:preset|spacing|40"}}}} -->
|
||||||
|
<div class="wp-block-columns is-layout-flex wp-container-core-columns">
|
||||||
|
<!-- wp:column -->
|
||||||
|
<div class="wp-block-column">
|
||||||
|
<!-- wp:group {"style":{"border":{"radius":"8px","width":"1px","color":"var:preset|color|light"},"spacing":{"padding":{"top":"0","bottom":"var:preset|spacing|40","left":"0","right":"0"}}},"layout":{"type":"constrained"}} -->
|
||||||
|
<div class="wp-block-group" style="border-color:var(--wp--preset--color--light);border-style:solid;border-width:1px;border-radius:8px;padding-bottom:var(--wp--preset--spacing--40)">
|
||||||
|
<!-- wp:image {"sizeSlug":"card-thumbnail","style":{"border":{"radius":{"topLeft":"8px","topRight":"8px"}}}} -->
|
||||||
|
<figure class="wp-block-image size-card-thumbnail" style="border-top-left-radius:8px;border-top-right-radius:8px"><img alt="" /></figure>
|
||||||
|
<!-- /wp:image -->
|
||||||
|
<!-- wp:group {"style":{"spacing":{"padding":{"left":"var:preset|spacing|40","right":"var:preset|spacing|40"}}}} -->
|
||||||
|
<div class="wp-block-group" style="padding-left:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40)">
|
||||||
|
<!-- wp:heading {"level":3,"style":{"spacing":{"margin":{"top":"var:preset|spacing|30"}}}} -->
|
||||||
|
<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--30)">' . esc_html__( '文章标题', 'cyywordpress' ) . '</h3>
|
||||||
|
<!-- /wp:heading -->
|
||||||
|
<!-- wp:paragraph {"style":{"color":{"text":"#6b7280"}}} -->
|
||||||
|
<p style="color:#6b7280">' . esc_html__( '文章摘要文字,简要介绍内容。', 'cyywordpress' ) . '</p>
|
||||||
|
<!-- /wp:paragraph -->
|
||||||
|
<!-- wp:read-more {"content":"' . esc_html__( '阅读全文 →', 'cyywordpress' ) . '"} /-->
|
||||||
|
</div>
|
||||||
|
<!-- /wp:group -->
|
||||||
|
</div>
|
||||||
|
<!-- /wp:group -->
|
||||||
|
</div>
|
||||||
|
<!-- /wp:column -->
|
||||||
|
<!-- wp:column -->
|
||||||
|
<div class="wp-block-column">
|
||||||
|
<!-- wp:group {"style":{"border":{"radius":"8px","width":"1px","color":"var:preset|color|light"},"spacing":{"padding":{"top":"0","bottom":"var:preset|spacing|40","left":"0","right":"0"}}},"layout":{"type":"constrained"}} -->
|
||||||
|
<div class="wp-block-group" style="border-color:var(--wp--preset--color--light);border-style:solid;border-width:1px;border-radius:8px;padding-bottom:var(--wp--preset--spacing--40)">
|
||||||
|
<!-- wp:image {"sizeSlug":"card-thumbnail","style":{"border":{"radius":{"topLeft":"8px","topRight":"8px"}}}} -->
|
||||||
|
<figure class="wp-block-image size-card-thumbnail" style="border-top-left-radius:8px;border-top-right-radius:8px"><img alt="" /></figure>
|
||||||
|
<!-- /wp:image -->
|
||||||
|
<!-- wp:group {"style":{"spacing":{"padding":{"left":"var:preset|spacing|40","right":"var:preset|spacing|40"}}}} -->
|
||||||
|
<div class="wp-block-group" style="padding-left:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40)">
|
||||||
|
<!-- wp:heading {"level":3,"style":{"spacing":{"margin":{"top":"var:preset|spacing|30"}}}} -->
|
||||||
|
<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--30)">' . esc_html__( '文章标题', 'cyywordpress' ) . '</h3>
|
||||||
|
<!-- /wp:heading -->
|
||||||
|
<!-- wp:paragraph {"style":{"color":{"text":"#6b7280"}}} -->
|
||||||
|
<p style="color:#6b7280">' . esc_html__( '文章摘要文字,简要介绍内容。', 'cyywordpress' ) . '</p>
|
||||||
|
<!-- /wp:paragraph -->
|
||||||
|
<!-- wp:read-more {"content":"' . esc_html__( '阅读全文 →', 'cyywordpress' ) . '"} /-->
|
||||||
|
</div>
|
||||||
|
<!-- /wp:group -->
|
||||||
|
</div>
|
||||||
|
<!-- /wp:group -->
|
||||||
|
</div>
|
||||||
|
<!-- /wp:column -->
|
||||||
|
<!-- wp:column -->
|
||||||
|
<div class="wp-block-column">
|
||||||
|
<!-- wp:group {"style":{"border":{"radius":"8px","width":"1px","color":"var:preset|color|light"},"spacing":{"padding":{"top":"0","bottom":"var:preset|spacing|40","left":"0","right":"0"}}},"layout":{"type":"constrained"}} -->
|
||||||
|
<div class="wp-block-group" style="border-color:var(--wp--preset--color--light);border-style:solid;border-width:1px;border-radius:8px;padding-bottom:var(--wp--preset--spacing--40)">
|
||||||
|
<!-- wp:image {"sizeSlug":"card-thumbnail","style":{"border":{"radius":{"topLeft":"8px","topRight":"8px"}}}} -->
|
||||||
|
<figure class="wp-block-image size-card-thumbnail" style="border-top-left-radius:8px;border-top-right-radius:8px"><img alt="" /></figure>
|
||||||
|
<!-- /wp:image -->
|
||||||
|
<!-- wp:group {"style":{"spacing":{"padding":{"left":"var:preset|spacing|40","right":"var:preset|spacing|40"}}}} -->
|
||||||
|
<div class="wp-block-group" style="padding-left:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40)">
|
||||||
|
<!-- wp:heading {"level":3,"style":{"spacing":{"margin":{"top":"var:preset|spacing|30"}}}} -->
|
||||||
|
<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--30)">' . esc_html__( '文章标题', 'cyywordpress' ) . '</h3>
|
||||||
|
<!-- /wp:heading -->
|
||||||
|
<!-- wp:paragraph {"style":{"color":{"text":"#6b7280"}}} -->
|
||||||
|
<p style="color:#6b7280">' . esc_html__( '文章摘要文字,简要介绍内容。', 'cyywordpress' ) . '</p>
|
||||||
|
<!-- /wp:paragraph -->
|
||||||
|
<!-- wp:read-more {"content":"' . esc_html__( '阅读全文 →', 'cyywordpress' ) . '"} /-->
|
||||||
|
</div>
|
||||||
|
<!-- /wp:group -->
|
||||||
|
</div>
|
||||||
|
<!-- /wp:group -->
|
||||||
|
</div>
|
||||||
|
<!-- /wp:column -->
|
||||||
|
</div>
|
||||||
|
<!-- /wp:columns -->
|
||||||
|
</div>
|
||||||
|
<!-- /wp:group -->',
|
||||||
|
)
|
||||||
|
);
|
||||||
+7
-3
@@ -61,7 +61,7 @@
|
|||||||
Multiple valid text domains can be provided as a comma-delimited list. -->
|
Multiple valid text domains can be provided as a comma-delimited list. -->
|
||||||
<rule ref="WordPress.WP.I18n">
|
<rule ref="WordPress.WP.I18n">
|
||||||
<properties>
|
<properties>
|
||||||
<property name="text_domain" type="array" value="_s"/>
|
<property name="text_domain" type="array" value="cyywordpress"/>
|
||||||
</properties>
|
</properties>
|
||||||
</rule>
|
</rule>
|
||||||
|
|
||||||
@@ -76,7 +76,11 @@
|
|||||||
<!-- Set the minimum supported WP version. This is used by several sniffs.
|
<!-- Set the minimum supported WP version. This is used by several sniffs.
|
||||||
The minimum version set here should be in line with the minimum WP version
|
The minimum version set here should be in line with the minimum WP version
|
||||||
as set in the "Requires at least" tag in the readme.txt file. -->
|
as set in the "Requires at least" tag in the readme.txt file. -->
|
||||||
<config name="minimum_supported_wp_version" value="4.5"/>
|
<config name="minimum_supported_wp_version" value="6.0"/>
|
||||||
|
|
||||||
|
<!-- PHP 8.1 compatibility check. -->
|
||||||
|
<config name="testVersion" value="8.1-"/>
|
||||||
|
<rule ref="PHPCompatibilityWP"/>
|
||||||
|
|
||||||
<rule ref="WordPress.Arrays.MultipleStatementAlignment">
|
<rule ref="WordPress.Arrays.MultipleStatementAlignment">
|
||||||
<properties>
|
<properties>
|
||||||
@@ -93,7 +97,7 @@
|
|||||||
Multiple valid prefixes can be provided as a comma-delimited list. -->
|
Multiple valid prefixes can be provided as a comma-delimited list. -->
|
||||||
<rule ref="WordPress.NamingConventions.PrefixAllGlobals">
|
<rule ref="WordPress.NamingConventions.PrefixAllGlobals">
|
||||||
<properties>
|
<properties>
|
||||||
<property name="prefixes" type="array" value="_s" />
|
<property name="prefixes" type="array" value="cyywordpress,CyyWordpress,Bulmapress" />
|
||||||
</properties>
|
</properties>
|
||||||
</rule>
|
</rule>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/** @type {import('postcss-load-config').Config} */
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
require( 'autoprefixer' ),
|
||||||
|
...( process.env.NODE_ENV === 'production'
|
||||||
|
? [ require( 'cssnano' )( { preset: 'default' } ) ]
|
||||||
|
: [] ),
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// abstracts/_functions.scss
|
||||||
|
// Pure SCSS helper functions.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// Convert px value to rem.
|
||||||
|
@function rem($px, $base: 16) {
|
||||||
|
@return #{$px / $base}rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip units from a value.
|
||||||
|
@function strip-unit($value) {
|
||||||
|
@return $value / ($value * 0 + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fluid type scale using clamp().
|
||||||
|
// Usage: font-size: fluid-type(16px, 24px);
|
||||||
|
@function fluid-type($min-size, $max-size, $min-vw: 375px, $max-vw: 1280px) {
|
||||||
|
$min-rem: rem(strip-unit($min-size));
|
||||||
|
$max-rem: rem(strip-unit($max-size));
|
||||||
|
|
||||||
|
@return clamp(
|
||||||
|
#{$min-rem},
|
||||||
|
calc(#{$min-rem} + (#{strip-unit($max-size)} - #{strip-unit($min-size)}) * ((100vw - #{$min-vw}) / (#{strip-unit($max-vw)} - #{strip-unit($min-vw)}))),
|
||||||
|
#{$max-rem}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// abstracts/_mixins.scss
|
||||||
|
// Responsive helpers and reusable patterns.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// Bulma-matching breakpoint mixins.
|
||||||
|
@mixin mobile {
|
||||||
|
@media screen and (max-width: 768px) { @content; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin tablet {
|
||||||
|
@media screen and (min-width: 769px) { @content; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin tablet-only {
|
||||||
|
@media screen and (min-width: 769px) and (max-width: 1023px) { @content; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin desktop {
|
||||||
|
@media screen and (min-width: 1024px) { @content; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin widescreen {
|
||||||
|
@media screen and (min-width: 1216px) { @content; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate text to N lines using -webkit-line-clamp.
|
||||||
|
@mixin line-clamp($lines: 2) {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: $lines;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visually hidden (accessible).
|
||||||
|
@mixin visually-hidden {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aspect-ratio box.
|
||||||
|
@mixin aspect-ratio($w, $h) {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
padding-top: percentage($h / $w);
|
||||||
|
}
|
||||||
|
|
||||||
|
> * {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focus visible ring (WCAG 2.1).
|
||||||
|
@mixin focus-ring($color: var(--color-primary)) {
|
||||||
|
&:focus-visible {
|
||||||
|
outline: 3px solid $color;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Card elevation with hover lift.
|
||||||
|
@mixin card-lift {
|
||||||
|
transition: transform var(--transition-base), box-shadow var(--transition-base);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: var(--card-shadow-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skeleton loading shimmer.
|
||||||
|
@mixin skeleton-shimmer {
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
var(--color-surface) 25%,
|
||||||
|
rgba(255, 255, 255, 0.5) 50%,
|
||||||
|
var(--color-surface) 75%
|
||||||
|
);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: skeleton-shimmer 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes skeleton-shimmer {
|
||||||
|
0% { background-position: 200% 0; }
|
||||||
|
100% { background-position: -200% 0; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// abstracts/_variables.scss
|
||||||
|
// CSS Custom Properties + SCSS variables for the 7-1 architecture.
|
||||||
|
// Bulma 1.x is built on CSS variables, so we map our design tokens here.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// --- Colour tokens -----------------------------------------------------------
|
||||||
|
:root {
|
||||||
|
--color-primary: #00d1b2;
|
||||||
|
--color-primary-dark: #00a896;
|
||||||
|
--color-dark: #363636;
|
||||||
|
--color-light: #f5f5f5;
|
||||||
|
--color-white: #ffffff;
|
||||||
|
--color-danger: #f14668;
|
||||||
|
--color-warning: #ffe08a;
|
||||||
|
--color-info: #3e8ed0;
|
||||||
|
--color-success: #48c78e;
|
||||||
|
|
||||||
|
// Semantic surface tokens (flipped in dark mode).
|
||||||
|
--color-background: #ffffff;
|
||||||
|
--color-text: #363636;
|
||||||
|
--color-surface: #f5f5f5;
|
||||||
|
--color-border: #dbdbdb;
|
||||||
|
--color-shadow: rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
// Navbar
|
||||||
|
--navbar-bg: rgba(255, 255, 255, 0.85);
|
||||||
|
--navbar-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||||
|
|
||||||
|
// Card
|
||||||
|
--card-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
--card-shadow-hover: 0 8px 24px rgba(0, 0, 0, 0.14);
|
||||||
|
--card-radius: 8px;
|
||||||
|
|
||||||
|
// Typography
|
||||||
|
--font-family-base: 'Noto Sans SC', system-ui, -apple-system, sans-serif;
|
||||||
|
--font-size-base: 1rem;
|
||||||
|
--line-height-base: 1.7;
|
||||||
|
|
||||||
|
// Spacing scale (mirrors theme.json)
|
||||||
|
--space-1: 0.25rem;
|
||||||
|
--space-2: 0.5rem;
|
||||||
|
--space-3: 1rem;
|
||||||
|
--space-4: 1.5rem;
|
||||||
|
--space-5: 2rem;
|
||||||
|
--space-6: 3rem;
|
||||||
|
--space-7: 4.5rem;
|
||||||
|
|
||||||
|
// Breakpoints (Bulma defaults)
|
||||||
|
--bp-mobile: 768px;
|
||||||
|
--bp-tablet: 769px;
|
||||||
|
--bp-desktop: 1024px;
|
||||||
|
--bp-widescreen: 1216px;
|
||||||
|
|
||||||
|
// Transitions
|
||||||
|
--transition-base: 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dark mode overrides via data-attribute or media query.
|
||||||
|
[data-theme='dark'],
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--color-background: #1a1a2e;
|
||||||
|
--color-text: #e0e0e0;
|
||||||
|
--color-surface: #16213e;
|
||||||
|
--color-border: #444444;
|
||||||
|
--color-shadow: rgba(0, 0, 0, 0.4);
|
||||||
|
--navbar-bg: rgba(26, 26, 46, 0.9);
|
||||||
|
--card-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
|
||||||
|
--card-shadow-hover:0 8px 24px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SCSS-only convenience aliases (not emitted as CSS).
|
||||||
|
$primary: #00d1b2 !default;
|
||||||
|
$primary-dark: #00a896 !default;
|
||||||
|
$dark: #363636 !default;
|
||||||
|
$font-base: var(--font-family-base);
|
||||||
|
$transition: var(--transition-base);
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// base/_reset.scss
|
||||||
|
// Minimal modern reset on top of Bulma's built-in normalisation.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--color-background);
|
||||||
|
color: var(--color-text);
|
||||||
|
font-family: var(--font-family-base);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
line-height: var(--line-height-base);
|
||||||
|
transition:
|
||||||
|
background-color var(--transition-base),
|
||||||
|
color var(--transition-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
video,
|
||||||
|
svg {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-primary);
|
||||||
|
text-underline-offset: 0.2em;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-primary-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include focus-ring;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove default list style for navigation lists.
|
||||||
|
nav ul,
|
||||||
|
nav ol {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// base/_typography.scss
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
line-height: 1.3;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
code,
|
||||||
|
pre {
|
||||||
|
font-family: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace;
|
||||||
|
font-size: 0.875em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: var(--space-3);
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-radius: var(--card-radius);
|
||||||
|
border-left: 4px solid var(--color-primary);
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
border-left: 4px solid var(--color-primary);
|
||||||
|
padding-left: var(--space-3);
|
||||||
|
margin-left: 0;
|
||||||
|
color: #6b7280;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.screen-reader-text {
|
||||||
|
@include visually-hidden;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background-color: var(--color-background);
|
||||||
|
clip: auto;
|
||||||
|
height: auto;
|
||||||
|
left: var(--space-3);
|
||||||
|
top: var(--space-3);
|
||||||
|
width: auto;
|
||||||
|
z-index: 100000;
|
||||||
|
padding: var(--space-2) var(--space-3);
|
||||||
|
border: 2px solid var(--color-primary);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// components/_button.scss
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// Extend Bulma's .button with our colour tokens.
|
||||||
|
.button.is-primary {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
border-color: transparent;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-primary-dark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy-code button on code blocks.
|
||||||
|
.copy-code-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem;
|
||||||
|
right: 0.5rem;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background var(--transition-base);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-copied {
|
||||||
|
color: var(--color-success);
|
||||||
|
border-color: var(--color-success);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// components/_card.scss
|
||||||
|
// Post card component.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
.post-card {
|
||||||
|
border-radius: var(--card-radius);
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--color-background);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
margin-bottom: var(--space-4);
|
||||||
|
transition:
|
||||||
|
transform var(--transition-base),
|
||||||
|
box-shadow var(--transition-base);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: var(--card-shadow-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Featured image wrapper.
|
||||||
|
&__image {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
transition: transform 0.5s ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover &__image img {
|
||||||
|
transform: scale(1.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gradient placeholder when no featured image.
|
||||||
|
&__gradient-placeholder {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(135deg, var(--color-primary), var(--color-info));
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__category-initial {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Categories.
|
||||||
|
&__categories {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--space-1);
|
||||||
|
margin-bottom: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__category-badge {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: opacity var(--transition-base);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title.
|
||||||
|
&__title {
|
||||||
|
margin-bottom: var(--space-2) !important;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-text);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color var(--transition-base);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meta row.
|
||||||
|
&__meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--space-3);
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
margin-bottom: var(--space-3) !important;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #6b7280;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Footer row.
|
||||||
|
&__footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--space-2);
|
||||||
|
margin-top: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Continue reading" button arrow animation.
|
||||||
|
&__read-more {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition:
|
||||||
|
background-color var(--transition-base),
|
||||||
|
color var(--transition-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__read-more-arrow {
|
||||||
|
display: inline-block;
|
||||||
|
transition: transform var(--transition-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__read-more:hover &__read-more-arrow {
|
||||||
|
transform: translateX(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hero variant (sticky posts).
|
||||||
|
&--hero {
|
||||||
|
.post-card__image {
|
||||||
|
max-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-card__title {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skeleton loading state.
|
||||||
|
&--loading {
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
.post-card__image,
|
||||||
|
.post-card__title,
|
||||||
|
.post-card__meta,
|
||||||
|
.post-card__content {
|
||||||
|
border-radius: 4px;
|
||||||
|
@include skeleton-shimmer;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-card__image {
|
||||||
|
height: 180px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// components/_navigation.scss
|
||||||
|
// Bulma navbar customisation + mobile dropdown corrections.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// Navbar item links.
|
||||||
|
.navbar-item,
|
||||||
|
.navbar-link {
|
||||||
|
color: var(--color-text);
|
||||||
|
transition: color var(--transition-base), background-color var(--transition-base);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus-visible {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dropdown.
|
||||||
|
.navbar-dropdown {
|
||||||
|
background: var(--color-background);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
|
||||||
|
.navbar-item {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-surface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-divider {
|
||||||
|
background-color: var(--color-border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Breadcrumb.
|
||||||
|
.breadcrumb {
|
||||||
|
margin: var(--space-3) 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-text);
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
transition: color var(--transition-base);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
li.is-active a {
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post navigation.
|
||||||
|
.nav-links {
|
||||||
|
.nav-previous,
|
||||||
|
.nav-next {
|
||||||
|
a {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-decoration: none;
|
||||||
|
max-width: 300px;
|
||||||
|
color: var(--color-text);
|
||||||
|
transition: color var(--transition-base);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-subtitle {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-title {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// components/_toc.scss
|
||||||
|
// Article Table of Contents sidebar widget.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
.toc {
|
||||||
|
background: var(--color-background);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--card-radius);
|
||||||
|
position: sticky;
|
||||||
|
top: 80px;
|
||||||
|
max-height: calc(100vh - 120px);
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
background: transparent;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header-title {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
padding: var(--space-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#toc-list {
|
||||||
|
ol {
|
||||||
|
list-style: decimal;
|
||||||
|
padding-left: var(--space-4);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: var(--space-2);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-text);
|
||||||
|
text-decoration: none;
|
||||||
|
line-height: 1.4;
|
||||||
|
transition: color var(--transition-base);
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.toc-active {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.toc-active {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second-level (h3).
|
||||||
|
.toc-level-3 {
|
||||||
|
padding-left: var(--space-3);
|
||||||
|
list-style: disc;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// layout/_footer.scss
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
.site-footer {
|
||||||
|
margin-top: var(--space-6);
|
||||||
|
background-color: var(--color-surface);
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
padding-top: var(--space-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-site-name {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: var(--space-2);
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-text);
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-site-description {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-menu-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-text);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color var(--transition-base);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-menu-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--space-2);
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 2.25rem;
|
||||||
|
height: 2.25rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--color-border);
|
||||||
|
color: var(--color-text);
|
||||||
|
transition:
|
||||||
|
background-color var(--transition-base),
|
||||||
|
color var(--transition-base);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-widget-title {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: var(--space-3);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-bottom {
|
||||||
|
margin-top: var(--space-5);
|
||||||
|
padding: var(--space-3) 0;
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-copyright {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-text);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// layout/_grid.scss
|
||||||
|
// Site-wide layout helpers.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
.site-content {
|
||||||
|
padding-top: var(--space-4);
|
||||||
|
padding-bottom: var(--space-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Primary content area + sidebar layout.
|
||||||
|
.content-area {
|
||||||
|
@include desktop {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-5);
|
||||||
|
|
||||||
|
#main {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0; // Prevent flex blowout.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsive post grid.
|
||||||
|
.posts-grid {
|
||||||
|
margin-bottom: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pagination.
|
||||||
|
.posts-pagination {
|
||||||
|
margin-top: var(--space-4);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.nav-links {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-2);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
span {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 2.25rem;
|
||||||
|
height: 2.25rem;
|
||||||
|
padding: 0 var(--space-2);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--color-text);
|
||||||
|
text-decoration: none;
|
||||||
|
transition:
|
||||||
|
background-color var(--transition-base),
|
||||||
|
color var(--transition-base),
|
||||||
|
border-color var(--transition-base);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.current {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: #fff;
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// layout/_header.scss
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
.site-navbar {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background-color: var(--navbar-bg);
|
||||||
|
box-shadow: var(--navbar-shadow);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
transition: box-shadow var(--transition-base), background-color var(--transition-base);
|
||||||
|
|
||||||
|
&.is-scrolled {
|
||||||
|
box-shadow: 0 4px 12px var(--color-shadow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logo area.
|
||||||
|
.navbar-logo {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-height: 2.5rem;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-site-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-dark) !important;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-title-initial {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hamburger burger – three-line → X animation.
|
||||||
|
.navbar-burger {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 0.5rem;
|
||||||
|
transition: opacity var(--transition-base);
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--color-text);
|
||||||
|
margin: 4px 0;
|
||||||
|
transition:
|
||||||
|
transform 0.3s ease,
|
||||||
|
opacity 0.3s ease;
|
||||||
|
transform-origin: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
|
||||||
|
&.is-active span:nth-child(2) { opacity: 0; }
|
||||||
|
&.is-active span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search widget.
|
||||||
|
.navbar-search-wrap {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-search-toggle {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--color-text);
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
transition: color var(--transition-base);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include focus-ring;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-search-form {
|
||||||
|
position: absolute;
|
||||||
|
top: 110%;
|
||||||
|
right: 0;
|
||||||
|
background: var(--color-background);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.5rem;
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
min-width: 200px;
|
||||||
|
|
||||||
|
.input {
|
||||||
|
border-color: var(--color-border);
|
||||||
|
background: var(--color-background);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dark-mode toggle.
|
||||||
|
.theme-toggle {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--color-text);
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
transition: color var(--transition-base);
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include focus-ring;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show only the relevant icon.
|
||||||
|
body:not([data-theme='dark']) .theme-toggle__light-icon { display: none; }
|
||||||
|
[data-theme='dark'] .theme-toggle__dark-icon { display: none; }
|
||||||
|
|
||||||
|
// Back-to-top button.
|
||||||
|
.back-to-top {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 2rem;
|
||||||
|
right: 2rem;
|
||||||
|
z-index: 200;
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
transition:
|
||||||
|
opacity var(--transition-base),
|
||||||
|
transform var(--transition-base),
|
||||||
|
box-shadow var(--transition-base);
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(1rem);
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
&.is-visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: var(--card-shadow-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include focus-ring;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip link.
|
||||||
|
.skip-link {
|
||||||
|
@include visually-hidden;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
position: fixed;
|
||||||
|
top: var(--space-2);
|
||||||
|
left: var(--space-2);
|
||||||
|
z-index: 9999;
|
||||||
|
background: var(--color-primary);
|
||||||
|
color: #fff;
|
||||||
|
padding: var(--space-2) var(--space-3);
|
||||||
|
border-radius: 4px;
|
||||||
|
clip: auto;
|
||||||
|
height: auto;
|
||||||
|
width: auto;
|
||||||
|
font-weight: 700;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// layout/_sidebar.scss
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
.widget-area {
|
||||||
|
.widget {
|
||||||
|
border-radius: var(--card-radius);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
padding-top: var(--space-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sticky sidebar on desktop.
|
||||||
|
@include desktop {
|
||||||
|
#secondary.widget-area {
|
||||||
|
position: sticky;
|
||||||
|
top: calc(var(--space-5) + 60px); // 60px = approx navbar height.
|
||||||
|
align-self: flex-start;
|
||||||
|
max-height: calc(100vh - 100px);
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// main.scss – CyyWordpress 7-1 SCSS entry point
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// 1. Abstracts (no CSS output, just variables / mixins / functions)
|
||||||
|
@use 'abstracts/variables';
|
||||||
|
@use 'abstracts/mixins' as *;
|
||||||
|
@use 'abstracts/functions';
|
||||||
|
|
||||||
|
// 2. Third-party framework (selective Bulma import)
|
||||||
|
@use 'vendors/bulma';
|
||||||
|
|
||||||
|
// 3. Base styles
|
||||||
|
@use 'base/reset';
|
||||||
|
@use 'base/typography';
|
||||||
|
|
||||||
|
// 4. Layout shells
|
||||||
|
@use 'layout/grid';
|
||||||
|
@use 'layout/header';
|
||||||
|
@use 'layout/footer';
|
||||||
|
@use 'layout/sidebar';
|
||||||
|
|
||||||
|
// 5. Reusable components
|
||||||
|
@use 'components/card';
|
||||||
|
@use 'components/navigation';
|
||||||
|
@use 'components/button';
|
||||||
|
@use 'components/toc';
|
||||||
|
|
||||||
|
// 6. Page-specific
|
||||||
|
@use 'pages/home';
|
||||||
|
@use 'pages/single';
|
||||||
|
@use 'pages/archive';
|
||||||
|
|
||||||
|
// 7. Themes (dark mode overrides)
|
||||||
|
@use 'themes/dark';
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// pages/_archive.scss
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
.archive-header {
|
||||||
|
margin-bottom: var(--space-4);
|
||||||
|
padding-bottom: var(--space-3);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.archive-description {
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// pages/_home.scss
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
.welcome-banner {
|
||||||
|
margin-bottom: var(--space-3);
|
||||||
|
border-radius: var(--card-radius);
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// pages/_single.scss
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// Reading progress bar.
|
||||||
|
.reading-progress {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 3px;
|
||||||
|
z-index: 9999;
|
||||||
|
background: transparent;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
&__bar {
|
||||||
|
height: 100%;
|
||||||
|
width: 0;
|
||||||
|
background: var(--color-primary);
|
||||||
|
transition: width 0.1s linear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hero featured image.
|
||||||
|
.single-hero {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 450px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: var(--card-radius);
|
||||||
|
margin-bottom: var(--space-4);
|
||||||
|
|
||||||
|
&__image {
|
||||||
|
width: 100%;
|
||||||
|
height: 450px;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
loading: eager;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(to top, rgba(0, 0, 0, 0.65) 0%, transparent 60%);
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
padding: var(--space-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
color: #fff;
|
||||||
|
font-size: clamp(1.5rem, 4vw, 2.5rem);
|
||||||
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single article layout.
|
||||||
|
.single-layout {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Social share.
|
||||||
|
.social-share {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: var(--space-4) 0;
|
||||||
|
padding: var(--space-3);
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-radius: var(--card-radius);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 2.25rem;
|
||||||
|
height: 2.25rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--color-border);
|
||||||
|
color: var(--color-text);
|
||||||
|
border: none;
|
||||||
|
font-size: 1rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition:
|
||||||
|
background-color var(--transition-base),
|
||||||
|
color var(--transition-base);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--weibo:hover { background-color: #e6162d; }
|
||||||
|
&--twitter:hover { background-color: #000; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Author bio.
|
||||||
|
.author-bio {
|
||||||
|
margin: var(--space-4) 0;
|
||||||
|
border-radius: var(--card-radius);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
|
||||||
|
.media {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__avatar {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
display: block;
|
||||||
|
color: var(--color-text);
|
||||||
|
text-decoration: none;
|
||||||
|
margin-bottom: var(--space-2);
|
||||||
|
|
||||||
|
&:hover { color: var(--color-primary); }
|
||||||
|
}
|
||||||
|
|
||||||
|
&__description {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #6b7280;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Related posts.
|
||||||
|
.related-posts {
|
||||||
|
margin: var(--space-4) 0;
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
margin-bottom: var(--space-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-post-card {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: var(--card-radius);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
overflow: hidden;
|
||||||
|
transition:
|
||||||
|
transform var(--transition-base),
|
||||||
|
box-shadow var(--transition-base);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--card-shadow-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text);
|
||||||
|
text-decoration: none;
|
||||||
|
line-height: 1.4;
|
||||||
|
|
||||||
|
&:hover { color: var(--color-primary); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code block copy button wrapper.
|
||||||
|
.entry-content pre,
|
||||||
|
.post-card__content pre {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// themes/_dark.scss
|
||||||
|
// Dark mode – extra overrides beyond CSS custom properties.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
[data-theme='dark'],
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
// Bulma component overrides.
|
||||||
|
.navbar {
|
||||||
|
background-color: var(--navbar-bg);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background-color: var(--color-surface);
|
||||||
|
border-color: var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification.is-primary {
|
||||||
|
background-color: rgba(0, 209, 178, 0.15);
|
||||||
|
color: #00d1b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input,
|
||||||
|
.select select,
|
||||||
|
.textarea {
|
||||||
|
background-color: var(--color-surface);
|
||||||
|
border-color: var(--color-border);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
background-color: var(--color-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code blocks.
|
||||||
|
pre,
|
||||||
|
code {
|
||||||
|
background-color: #0d1117;
|
||||||
|
color: #e6edf3;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+10
@@ -0,0 +1,10 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// vendors/_bulma.scss
|
||||||
|
// Selective Bulma 1.x import – only import what the theme uses.
|
||||||
|
// Bulma 1.x exposes sass/ subdirectory; each module uses @use.
|
||||||
|
// =============================================================================
|
||||||
|
@use 'bulma/sass' with (
|
||||||
|
$primary: #00d1b2,
|
||||||
|
$dark: #363636,
|
||||||
|
$light: #f5f5f5
|
||||||
|
);
|
||||||
+7
-4
@@ -1,6 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* The sidebar containing the main widget area
|
* The sidebar containing the main widget area.
|
||||||
|
*
|
||||||
|
* Fixed: each widget is wrapped by the correct Bulma card structure
|
||||||
|
* registered in functions.php (before_widget / after_widget).
|
||||||
*
|
*
|
||||||
* @link https://developer.wordpress.org/themes/basics/template-files/#template-partials
|
* @link https://developer.wordpress.org/themes/basics/template-files/#template-partials
|
||||||
*
|
*
|
||||||
@@ -12,7 +15,7 @@ if ( ! is_active_sidebar( 'sidebar-1' ) ) {
|
|||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="columns" style="margin-bottom: 10px;">
|
<aside id="secondary" class="widget-area column is-3" role="complementary" aria-label="<?php esc_attr_e( '侧边栏', 'cyywordpress' ); ?>">
|
||||||
<?php dynamic_sidebar( 'sidebar-1' ); ?>
|
<?php dynamic_sidebar( 'sidebar-1' ); ?>
|
||||||
|
</aside><!-- #secondary -->
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|||||||
+216
-39
@@ -1,6 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* The template for displaying all single posts
|
* The template for displaying single posts
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* – Full-width hero featured image with title overlay
|
||||||
|
* – Reading progress bar (filled by reading-progress.ts)
|
||||||
|
* – Auto-generated TOC (filled by toc.ts)
|
||||||
|
* – Author bio card
|
||||||
|
* – Related posts (same category)
|
||||||
|
* – Social share buttons (no third-party SDK)
|
||||||
|
* – Breadcrumb, prev/next navigation, comments
|
||||||
*
|
*
|
||||||
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/#single-post
|
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/#single-post
|
||||||
*
|
*
|
||||||
@@ -9,56 +18,224 @@
|
|||||||
|
|
||||||
get_header();
|
get_header();
|
||||||
?>
|
?>
|
||||||
<br>
|
|
||||||
<div class="notification is-primary">
|
|
||||||
欢迎来到这! <strong>Hello everyone.</strong>
|
|
||||||
</div>
|
|
||||||
<nav class="breadcrumb" aria-label="breadcrumbs">
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="<?php echo esc_url( home_url( '/' ) ); ?>" >
|
|
||||||
<span class="icon is-small">
|
|
||||||
<i class="fas fa-home" aria-hidden="true"></i>
|
|
||||||
</span>
|
|
||||||
<span>首页</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#">
|
|
||||||
<span class="icon is-small">
|
|
||||||
<i class="fas fa-book" aria-hidden="true"></i>
|
|
||||||
</span>
|
|
||||||
<span>文章页</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
|
<!-- Reading progress bar -->
|
||||||
|
<div id="reading-progress" class="reading-progress" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" aria-label="<?php esc_attr_e( '阅读进度', 'cyywordpress' ); ?>">
|
||||||
|
<div class="reading-progress__bar"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<main id="primary" class="site-main">
|
<div id="primary" class="content-area">
|
||||||
|
<main id="main" class="site-main" role="main">
|
||||||
|
|
||||||
|
<nav class="breadcrumb" aria-label="<?php esc_attr_e( '面包屑导航', 'cyywordpress' ); ?>">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="<?php echo esc_url( home_url( '/' ) ); ?>">
|
||||||
|
<span class="icon is-small"><i class="fa-solid fa-house" aria-hidden="true"></i></span>
|
||||||
|
<span><?php esc_html_e( '首页', 'cyywordpress' ); ?></span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="is-active">
|
||||||
|
<a href="#" aria-current="page">
|
||||||
|
<span class="icon is-small"><i class="fa-solid fa-file-lines" aria-hidden="true"></i></span>
|
||||||
|
<span><?php esc_html_e( '文章', 'cyywordpress' ); ?></span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
while ( have_posts() ) :
|
while ( have_posts() ) :
|
||||||
the_post();
|
the_post();
|
||||||
|
?>
|
||||||
|
|
||||||
get_template_part( 'template-parts/content', get_post_type() );
|
<?php if ( has_post_thumbnail() ) : ?>
|
||||||
|
<div class="single-hero">
|
||||||
|
<?php
|
||||||
|
the_post_thumbnail(
|
||||||
|
'full',
|
||||||
|
array(
|
||||||
|
'class' => 'single-hero__image',
|
||||||
|
'loading' => 'eager',
|
||||||
|
'decoding'=> 'async',
|
||||||
|
'alt' => the_title_attribute( array( 'echo' => false ) ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
<div class="single-hero__overlay">
|
||||||
|
<?php the_title( '<h1 class="single-hero__title">', '</h1>' ); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
the_post_navigation(
|
<div class="columns single-layout">
|
||||||
array(
|
<!-- TOC sidebar (populated by toc.ts) -->
|
||||||
'prev_text' => '<span class="nav-subtitle">' . esc_html__( '上一篇:', 'cyywordpress' ) . '</span> <span class="nav-title">%title</span>',
|
<aside class="column is-3-desktop is-hidden-touch single-toc" aria-label="<?php esc_attr_e( '目录', 'cyywordpress' ); ?>">
|
||||||
'next_text' => '<span class="nav-subtitle">' . esc_html__( '下一篇:', 'cyywordpress' ) . '</span> <span class="nav-title">%title</span>',
|
<div id="toc" class="toc card">
|
||||||
)
|
<div class="card-header">
|
||||||
);
|
<p class="card-header-title">
|
||||||
|
<i class="fa-solid fa-list" aria-hidden="true"></i>
|
||||||
|
<?php esc_html_e( '目录', 'cyywordpress' ); ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-content" id="toc-list"></div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
// If comments are open or we have at least one comment, load up the comment template.
|
<!-- Main article column -->
|
||||||
if ( comments_open() || get_comments_number() ) :
|
<div class="column is-9-desktop single-article">
|
||||||
comments_template();
|
<?php get_template_part( 'template-parts/content', get_post_type() ); ?>
|
||||||
endif;
|
|
||||||
|
|
||||||
endwhile; // End of the loop.
|
<!-- Social share -->
|
||||||
?>
|
<div class="social-share">
|
||||||
|
<span class="social-share__label"><?php esc_html_e( '分享:', 'cyywordpress' ); ?></span>
|
||||||
|
<a
|
||||||
|
class="social-share__btn social-share__btn--weibo"
|
||||||
|
href="<?php echo esc_url( 'https://service.weibo.com/share/share.php?url=' . rawurlencode( get_permalink() ) . '&title=' . rawurlencode( get_the_title() ) ); ?>"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label="<?php esc_attr_e( '分享到微博', 'cyywordpress' ); ?>"
|
||||||
|
>
|
||||||
|
<i class="fa-brands fa-weibo" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="social-share__btn social-share__btn--twitter"
|
||||||
|
href="<?php echo esc_url( 'https://twitter.com/intent/tweet?url=' . rawurlencode( get_permalink() ) . '&text=' . rawurlencode( get_the_title() ) ); ?>"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label="<?php esc_attr_e( '分享到 Twitter', 'cyywordpress' ); ?>"
|
||||||
|
>
|
||||||
|
<i class="fa-brands fa-x-twitter" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
class="social-share__btn social-share__btn--copy"
|
||||||
|
data-url="<?php echo esc_attr( get_permalink() ); ?>"
|
||||||
|
aria-label="<?php esc_attr_e( '复制链接', 'cyywordpress' ); ?>"
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-link" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Post navigation -->
|
||||||
|
<?php
|
||||||
|
the_post_navigation(
|
||||||
|
array(
|
||||||
|
'prev_text' => '<span class="nav-subtitle"><i class="fa-solid fa-chevron-left" aria-hidden="true"></i> ' . esc_html__( '上一篇', 'cyywordpress' ) . '</span><span class="nav-title">%title</span>',
|
||||||
|
'next_text' => '<span class="nav-subtitle">' . esc_html__( '下一篇', 'cyywordpress' ) . ' <i class="fa-solid fa-chevron-right" aria-hidden="true"></i></span><span class="nav-title">%title</span>',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- Author bio -->
|
||||||
|
<?php
|
||||||
|
$author_id = (int) get_the_author_meta( 'ID' );
|
||||||
|
$author_bio = get_the_author_meta( 'description' );
|
||||||
|
if ( $author_bio ) :
|
||||||
|
?>
|
||||||
|
<div class="author-bio card">
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="media">
|
||||||
|
<div class="media-left">
|
||||||
|
<?php
|
||||||
|
echo get_avatar(
|
||||||
|
$author_id,
|
||||||
|
80,
|
||||||
|
'',
|
||||||
|
get_the_author(),
|
||||||
|
array(
|
||||||
|
'class' => 'author-bio__avatar',
|
||||||
|
'loading' => 'lazy',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
<div class="media-content">
|
||||||
|
<a class="author-bio__name title is-5" href="<?php echo esc_url( get_author_posts_url( $author_id ) ); ?>">
|
||||||
|
<?php echo esc_html( get_the_author() ); ?>
|
||||||
|
</a>
|
||||||
|
<p class="author-bio__description"><?php echo esc_html( $author_bio ); ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div><!-- .author-bio -->
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Related posts (same first category) -->
|
||||||
|
<?php
|
||||||
|
$cats = get_the_category();
|
||||||
|
if ( $cats ) :
|
||||||
|
$related = get_posts(
|
||||||
|
array(
|
||||||
|
'category__in' => array( $cats[0]->term_id ),
|
||||||
|
'post__not_in' => array( get_the_ID() ),
|
||||||
|
'posts_per_page' => 3,
|
||||||
|
'orderby' => 'rand',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( $related ) :
|
||||||
|
?>
|
||||||
|
<div class="related-posts">
|
||||||
|
<h3 class="related-posts__title title is-5">
|
||||||
|
<?php esc_html_e( '相关文章', 'cyywordpress' ); ?>
|
||||||
|
</h3>
|
||||||
|
<div class="columns is-multiline">
|
||||||
|
<?php
|
||||||
|
foreach ( $related as $related_post ) :
|
||||||
|
setup_postdata( $related_post );
|
||||||
|
?>
|
||||||
|
<div class="column is-4">
|
||||||
|
<div class="card related-post-card">
|
||||||
|
<?php if ( has_post_thumbnail( $related_post ) ) : ?>
|
||||||
|
<div class="card-image">
|
||||||
|
<figure class="image is-16by9">
|
||||||
|
<?php
|
||||||
|
echo get_the_post_thumbnail(
|
||||||
|
$related_post,
|
||||||
|
'card-thumbnail-sm',
|
||||||
|
array(
|
||||||
|
'loading' => 'lazy',
|
||||||
|
'decoding' => 'async',
|
||||||
|
'class' => 'related-post-card__image',
|
||||||
|
'alt' => esc_attr( get_the_title( $related_post ) ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<div class="card-content">
|
||||||
|
<a class="related-post-card__title" href="<?php echo esc_url( get_permalink( $related_post ) ); ?>">
|
||||||
|
<?php echo esc_html( get_the_title( $related_post ) ); ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
endforeach;
|
||||||
|
wp_reset_postdata();
|
||||||
|
?>
|
||||||
|
</div><!-- .columns -->
|
||||||
|
</div><!-- .related-posts -->
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Comments -->
|
||||||
|
<?php
|
||||||
|
if ( comments_open() || get_comments_number() ) :
|
||||||
|
comments_template();
|
||||||
|
endif;
|
||||||
|
?>
|
||||||
|
|
||||||
|
</div><!-- .single-article -->
|
||||||
|
</div><!-- .columns.single-layout -->
|
||||||
|
|
||||||
|
<?php endwhile; ?>
|
||||||
|
|
||||||
</main><!-- #main -->
|
</main><!-- #main -->
|
||||||
|
</div><!-- #primary -->
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
get_sidebar();
|
get_sidebar();
|
||||||
get_footer();
|
get_footer();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* customizer.ts (replaces js/customizer.js)
|
||||||
|
* Binds postMessage handlers for the WordPress Customizer live preview.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare const wp: {
|
||||||
|
customize: {
|
||||||
|
bind( event: string, callback: () => void ): void;
|
||||||
|
( settingId: string, callback: ( value: { get(): string; bind( cb: ( v: string ) => void ): void } ) => void ): void;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
( function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
wp.customize( 'blogname', ( value ) => {
|
||||||
|
value.bind( ( to: string ) => {
|
||||||
|
const el = document.querySelector<HTMLElement>( '.site-title a' );
|
||||||
|
if ( el ) el.textContent = to;
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
wp.customize( 'blogdescription', ( value ) => {
|
||||||
|
value.bind( ( to: string ) => {
|
||||||
|
const el = document.querySelector<HTMLElement>( '.site-description' );
|
||||||
|
if ( el ) el.textContent = to;
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
wp.customize( 'header_textcolor', ( value ) => {
|
||||||
|
value.bind( ( to: string ) => {
|
||||||
|
const els = document.querySelectorAll<HTMLElement>( '.site-title a, .site-description' );
|
||||||
|
els.forEach( ( el ) => {
|
||||||
|
el.style.color = to === 'blank' ? 'transparent' : to;
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
wp.customize( 'cyywordpress_welcome_text', ( value ) => {
|
||||||
|
value.bind( ( to: string ) => {
|
||||||
|
const el = document.querySelector<HTMLElement>( '.welcome-banner' );
|
||||||
|
if ( el ) el.textContent = to;
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
} )();
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* back-to-top.ts
|
||||||
|
* Shows a back-to-top button after scrolling 300 px;
|
||||||
|
* scrolls to top using smooth behaviour on click.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const SCROLL_THRESHOLD = 300;
|
||||||
|
|
||||||
|
export function initBackToTop(): void {
|
||||||
|
const btn = document.getElementById( 'back-to-top' ) as HTMLButtonElement | null;
|
||||||
|
if ( ! btn ) return;
|
||||||
|
|
||||||
|
// Remove the `hidden` attribute so CSS can control visibility via `.is-visible`.
|
||||||
|
btn.removeAttribute( 'hidden' );
|
||||||
|
|
||||||
|
const onScroll = (): void => {
|
||||||
|
btn.classList.toggle( 'is-visible', window.scrollY > SCROLL_THRESHOLD );
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener( 'scroll', onScroll, { passive: true } );
|
||||||
|
onScroll();
|
||||||
|
|
||||||
|
btn.addEventListener( 'click', () => {
|
||||||
|
window.scrollTo( { top: 0, behavior: 'smooth' } );
|
||||||
|
btn.blur();
|
||||||
|
} );
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* copy-code.ts
|
||||||
|
* Adds a "Copy" button to every <pre><code> block in the article content.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function initCopyCode(): void {
|
||||||
|
const blocks = document.querySelectorAll<HTMLPreElement>(
|
||||||
|
'.entry-content pre, .post-card__content pre, .single-article pre'
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( ! blocks.length ) return;
|
||||||
|
|
||||||
|
blocks.forEach( ( pre ) => {
|
||||||
|
// Avoid adding duplicate buttons.
|
||||||
|
if ( pre.querySelector( '.copy-code-btn' ) ) return;
|
||||||
|
|
||||||
|
const btn = document.createElement( 'button' );
|
||||||
|
btn.className = 'copy-code-btn';
|
||||||
|
btn.textContent = '复制';
|
||||||
|
btn.setAttribute( 'aria-label', '复制代码' );
|
||||||
|
btn.type = 'button';
|
||||||
|
|
||||||
|
btn.addEventListener( 'click', async () => {
|
||||||
|
const code = pre.querySelector( 'code' );
|
||||||
|
const text = code?.textContent ?? pre.textContent ?? '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText( text );
|
||||||
|
btn.textContent = '已复制!';
|
||||||
|
btn.classList.add( 'is-copied' );
|
||||||
|
setTimeout( () => {
|
||||||
|
btn.textContent = '复制';
|
||||||
|
btn.classList.remove( 'is-copied' );
|
||||||
|
}, 2000 );
|
||||||
|
} catch {
|
||||||
|
btn.textContent = '失败';
|
||||||
|
setTimeout( () => { btn.textContent = '复制'; }, 2000 );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
pre.style.position = 'relative';
|
||||||
|
pre.appendChild( btn );
|
||||||
|
} );
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* lazy-images.ts
|
||||||
|
* Custom lazy-loading via IntersectionObserver for images
|
||||||
|
* that use data-src / data-srcset fallback (legacy browsers or pre-rendered markup).
|
||||||
|
* Native `loading="lazy"` is used for all new images via PHP;
|
||||||
|
* this module handles any remaining cases.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function initLazyImages(): void {
|
||||||
|
if ( ! ( 'IntersectionObserver' in window ) ) return;
|
||||||
|
|
||||||
|
const images = document.querySelectorAll<HTMLImageElement>( 'img[data-src]' );
|
||||||
|
if ( ! images.length ) return;
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
( entries, obs ) => {
|
||||||
|
entries.forEach( ( entry ) => {
|
||||||
|
if ( ! entry.isIntersecting ) return;
|
||||||
|
|
||||||
|
const img = entry.target as HTMLImageElement;
|
||||||
|
|
||||||
|
if ( img.dataset.src ) {
|
||||||
|
img.src = img.dataset.src;
|
||||||
|
delete img.dataset.src;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( img.dataset.srcset ) {
|
||||||
|
img.srcset = img.dataset.srcset;
|
||||||
|
delete img.dataset.srcset;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.classList.add( 'is-loaded' );
|
||||||
|
obs.unobserve( img );
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
{ rootMargin: '200px 0px' }
|
||||||
|
);
|
||||||
|
|
||||||
|
images.forEach( ( img ) => observer.observe( img ) );
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* main.ts
|
||||||
|
* Entry point – conditionally imports each module based on the page context.
|
||||||
|
*
|
||||||
|
* Import the SCSS entry point here so Vite bundles both together.
|
||||||
|
*/
|
||||||
|
import '@scss/main.scss';
|
||||||
|
|
||||||
|
import { initNavigation } from './navigation';
|
||||||
|
import { initThemeToggle } from './theme-toggle';
|
||||||
|
import { initBackToTop } from './back-to-top';
|
||||||
|
import { initLazyImages } from './lazy-images';
|
||||||
|
|
||||||
|
// Single-post-only modules (guard by DOM presence to keep bundles lean).
|
||||||
|
async function loadSingleModules(): Promise<void> {
|
||||||
|
if ( ! document.querySelector( '.single-article' ) ) return;
|
||||||
|
|
||||||
|
const [ { initReadingProgress }, { initToc }, { initCopyCode } ] = await Promise.all( [
|
||||||
|
import( './reading-progress' ),
|
||||||
|
import( './toc' ),
|
||||||
|
import( './copy-code' ),
|
||||||
|
] );
|
||||||
|
|
||||||
|
initReadingProgress();
|
||||||
|
initToc();
|
||||||
|
initCopyCode();
|
||||||
|
|
||||||
|
// Social share – copy link button.
|
||||||
|
const copyLinkBtns = document.querySelectorAll<HTMLButtonElement>( '.social-share__btn--copy' );
|
||||||
|
copyLinkBtns.forEach( ( btn ) => {
|
||||||
|
btn.addEventListener( 'click', async () => {
|
||||||
|
const url = btn.dataset.url ?? window.location.href;
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText( url );
|
||||||
|
btn.setAttribute( 'aria-label', '链接已复制!' );
|
||||||
|
setTimeout( () => btn.setAttribute( 'aria-label', '复制链接' ), 2000 );
|
||||||
|
} catch {
|
||||||
|
// Clipboard API unavailable – silent fail.
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener( 'DOMContentLoaded', () => {
|
||||||
|
// Apply saved theme before paint to avoid FOUC (also done inline in theme-toggle).
|
||||||
|
initThemeToggle();
|
||||||
|
initNavigation();
|
||||||
|
initBackToTop();
|
||||||
|
initLazyImages();
|
||||||
|
void loadSingleModules();
|
||||||
|
} );
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* navigation.ts
|
||||||
|
* Handles the Bulma navbar burger toggle + keyboard nav for dropdowns.
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface NavbarBurger extends HTMLElement {
|
||||||
|
dataset: DOMStringMap & { target?: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initNavigation(): void {
|
||||||
|
// Hamburger burger toggle.
|
||||||
|
const burgers = document.querySelectorAll<NavbarBurger>( '.navbar-burger' );
|
||||||
|
|
||||||
|
burgers.forEach( ( burger ) => {
|
||||||
|
burger.addEventListener( 'click', () => {
|
||||||
|
const targetId = burger.dataset.target ?? burger.getAttribute( 'aria-controls' ) ?? '';
|
||||||
|
const menu = document.getElementById( targetId );
|
||||||
|
|
||||||
|
if ( ! menu ) return;
|
||||||
|
|
||||||
|
const isExpanded = burger.getAttribute( 'aria-expanded' ) === 'true';
|
||||||
|
burger.setAttribute( 'aria-expanded', String( ! isExpanded ) );
|
||||||
|
burger.classList.toggle( 'is-active' );
|
||||||
|
menu.classList.toggle( 'is-active' );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Keyboard support for dropdown menus (Tab / Escape).
|
||||||
|
const dropdowns = document.querySelectorAll<HTMLElement>( '.has-dropdown' );
|
||||||
|
|
||||||
|
dropdowns.forEach( ( dropdown ) => {
|
||||||
|
const trigger = dropdown.querySelector<HTMLElement>( '.navbar-link' );
|
||||||
|
const menu = dropdown.querySelector<HTMLElement>( '.navbar-dropdown' );
|
||||||
|
|
||||||
|
if ( ! trigger || ! menu ) return;
|
||||||
|
|
||||||
|
// Close when Escape is pressed inside the dropdown.
|
||||||
|
dropdown.addEventListener( 'keydown', ( e: KeyboardEvent ) => {
|
||||||
|
if ( e.key === 'Escape' ) {
|
||||||
|
trigger.focus();
|
||||||
|
dropdown.classList.remove( 'is-active' );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Close when focus leaves the dropdown.
|
||||||
|
dropdown.addEventListener( 'focusout', ( e: FocusEvent ) => {
|
||||||
|
if ( ! dropdown.contains( e.relatedTarget as Node ) ) {
|
||||||
|
dropdown.classList.remove( 'is-active' );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
trigger.addEventListener( 'keydown', ( e: KeyboardEvent ) => {
|
||||||
|
if ( e.key === 'Enter' || e.key === ' ' ) {
|
||||||
|
e.preventDefault();
|
||||||
|
dropdown.classList.toggle( 'is-active' );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Sticky navbar scroll class.
|
||||||
|
const navbar = document.querySelector<HTMLElement>( '.site-navbar' );
|
||||||
|
|
||||||
|
if ( navbar ) {
|
||||||
|
const onScroll = (): void => {
|
||||||
|
navbar.classList.toggle( 'is-scrolled', window.scrollY > 10 );
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener( 'scroll', onScroll, { passive: true } );
|
||||||
|
onScroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navbar search toggle.
|
||||||
|
const searchToggle = document.querySelector<HTMLButtonElement>( '.navbar-search-toggle' );
|
||||||
|
const searchForm = document.getElementById( 'navbar-search-form' );
|
||||||
|
|
||||||
|
if ( searchToggle && searchForm ) {
|
||||||
|
searchToggle.addEventListener( 'click', () => {
|
||||||
|
const isHidden = searchForm.hidden;
|
||||||
|
searchForm.hidden = ! isHidden;
|
||||||
|
searchToggle.setAttribute( 'aria-expanded', String( isHidden ) );
|
||||||
|
|
||||||
|
if ( isHidden ) {
|
||||||
|
const input = searchForm.querySelector<HTMLInputElement>( 'input' );
|
||||||
|
input?.focus();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Close when clicking outside.
|
||||||
|
document.addEventListener( 'click', ( e: MouseEvent ) => {
|
||||||
|
const target = e.target as Node;
|
||||||
|
if ( ! searchToggle.contains( target ) && ! searchForm.contains( target ) ) {
|
||||||
|
searchForm.hidden = true;
|
||||||
|
searchToggle.setAttribute( 'aria-expanded', 'false' );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* reading-progress.ts
|
||||||
|
* Fills a thin progress bar at the top of single-post pages
|
||||||
|
* based on how far down the user has scrolled.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function initReadingProgress(): void {
|
||||||
|
const progressEl = document.querySelector<HTMLElement>( '.reading-progress' );
|
||||||
|
const barEl = document.querySelector<HTMLElement>( '.reading-progress__bar' );
|
||||||
|
if ( ! progressEl || ! barEl ) return;
|
||||||
|
|
||||||
|
const article = document.querySelector<HTMLElement>( '.single-article' ) ?? document.body;
|
||||||
|
|
||||||
|
const update = (): void => {
|
||||||
|
const articleTop = article.getBoundingClientRect().top + window.scrollY;
|
||||||
|
const articleHeight = article.offsetHeight;
|
||||||
|
const scrolled = window.scrollY - articleTop;
|
||||||
|
const progress = Math.min( 100, Math.max( 0, ( scrolled / articleHeight ) * 100 ) );
|
||||||
|
|
||||||
|
barEl.style.width = `${ progress }%`;
|
||||||
|
progressEl.setAttribute( 'aria-valuenow', String( Math.round( progress ) ) );
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener( 'scroll', update, { passive: true } );
|
||||||
|
update();
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* theme-toggle.ts
|
||||||
|
* Dark-mode toggle with localStorage persistence + system preference detection.
|
||||||
|
*/
|
||||||
|
|
||||||
|
type Theme = 'light' | 'dark';
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'cyywordpress-theme';
|
||||||
|
|
||||||
|
function getSystemTheme(): Theme {
|
||||||
|
return window.matchMedia( '(prefers-color-scheme: dark)' ).matches ? 'dark' : 'light';
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTheme( theme: Theme ): void {
|
||||||
|
document.documentElement.setAttribute( 'data-theme', theme );
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSavedTheme(): Theme | null {
|
||||||
|
const saved = localStorage.getItem( STORAGE_KEY );
|
||||||
|
return saved === 'dark' || saved === 'light' ? saved : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initThemeToggle(): void {
|
||||||
|
// Apply saved or system theme immediately (before paint).
|
||||||
|
const initialTheme = getSavedTheme() ?? getSystemTheme();
|
||||||
|
applyTheme( initialTheme );
|
||||||
|
|
||||||
|
const btn = document.getElementById( 'theme-toggle' ) as HTMLButtonElement | null;
|
||||||
|
if ( ! btn ) return;
|
||||||
|
|
||||||
|
btn.addEventListener( 'click', () => {
|
||||||
|
const current = document.documentElement.getAttribute( 'data-theme' ) as Theme | null;
|
||||||
|
const next: Theme = current === 'dark' ? 'light' : 'dark';
|
||||||
|
applyTheme( next );
|
||||||
|
localStorage.setItem( STORAGE_KEY, next );
|
||||||
|
btn.setAttribute( 'aria-label', next === 'dark' ? '切换浅色模式' : '切换深色模式' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Respond to OS-level change when user hasn't set a preference.
|
||||||
|
window.matchMedia( '(prefers-color-scheme: dark)' ).addEventListener( 'change', ( e ) => {
|
||||||
|
if ( getSavedTheme() === null ) {
|
||||||
|
applyTheme( e.matches ? 'dark' : 'light' );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
/**
|
||||||
|
* toc.ts
|
||||||
|
* Automatically generates a Table of Contents from h2/h3 headings
|
||||||
|
* in the article body and highlights the active heading while scrolling.
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface TocItem {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
level: 2 | 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initToc(): void {
|
||||||
|
const tocList = document.getElementById( 'toc-list' );
|
||||||
|
const article = document.querySelector<HTMLElement>( '.single-article .entry-content, .single-article .post-card__content' );
|
||||||
|
|
||||||
|
if ( ! tocList || ! article ) return;
|
||||||
|
|
||||||
|
const headings = Array.from(
|
||||||
|
article.querySelectorAll<HTMLHeadingElement>( 'h2, h3' )
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( headings.length < 2 ) {
|
||||||
|
// Hide TOC widget when there aren't enough headings.
|
||||||
|
const tocWidget = document.getElementById( 'toc' );
|
||||||
|
if ( tocWidget ) tocWidget.hidden = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const items: TocItem[] = headings.map( ( heading ) => {
|
||||||
|
if ( ! heading.id ) {
|
||||||
|
// Generate a slug from heading text.
|
||||||
|
heading.id = heading.textContent
|
||||||
|
?.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replace( /[^\w\u4e00-\u9fa5\s-]/g, '' )
|
||||||
|
.replace( /\s+/g, '-' )
|
||||||
|
.slice( 0, 60 ) ?? `heading-${ Math.random().toString( 36 ).slice( 2 ) }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: heading.id,
|
||||||
|
text: heading.textContent?.trim() ?? '',
|
||||||
|
level: heading.tagName === 'H2' ? 2 : 3,
|
||||||
|
};
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Build ordered list HTML.
|
||||||
|
const ol = document.createElement( 'ol' );
|
||||||
|
let currentUl: HTMLUListElement | null = null;
|
||||||
|
|
||||||
|
items.forEach( ( item ) => {
|
||||||
|
const li = document.createElement( 'li' );
|
||||||
|
const link = document.createElement( 'a' );
|
||||||
|
link.href = `#${ item.id }`;
|
||||||
|
link.textContent = item.text;
|
||||||
|
link.className = `toc-link toc-level-${ item.level }`;
|
||||||
|
|
||||||
|
link.addEventListener( 'click', ( e ) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const target = document.getElementById( item.id );
|
||||||
|
target?.scrollIntoView( { behavior: 'smooth', block: 'start' } );
|
||||||
|
} );
|
||||||
|
|
||||||
|
li.appendChild( link );
|
||||||
|
|
||||||
|
if ( item.level === 2 ) {
|
||||||
|
currentUl = null;
|
||||||
|
ol.appendChild( li );
|
||||||
|
} else {
|
||||||
|
// Nest h3 items under the parent h2 list item.
|
||||||
|
if ( ! currentUl ) {
|
||||||
|
currentUl = document.createElement( 'ul' );
|
||||||
|
currentUl.className = 'toc-sub-list';
|
||||||
|
const lastH2 = ol.querySelector( 'li:last-child' );
|
||||||
|
lastH2?.appendChild( currentUl );
|
||||||
|
}
|
||||||
|
currentUl.appendChild( li );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
tocList.appendChild( ol );
|
||||||
|
|
||||||
|
// Intersection Observer – highlight active heading.
|
||||||
|
const links = tocList.querySelectorAll<HTMLAnchorElement>( '.toc-link' );
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
( entries ) => {
|
||||||
|
entries.forEach( ( entry ) => {
|
||||||
|
const id = entry.target.getAttribute( 'id' ) ?? '';
|
||||||
|
const link = tocList.querySelector<HTMLAnchorElement>( `a[href="#${ id }"]` );
|
||||||
|
if ( link ) {
|
||||||
|
link.classList.toggle( 'toc-active', entry.isIntersecting );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rootMargin: '-80px 0px -60% 0px',
|
||||||
|
threshold: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
headings.forEach( ( h ) => observer.observe( h ) );
|
||||||
|
}
|
||||||
@@ -4,9 +4,9 @@ Theme URI: http://underscores.me/
|
|||||||
Author: Underscores.me
|
Author: Underscores.me
|
||||||
Author URI: http://underscores.me/
|
Author URI: http://underscores.me/
|
||||||
Description: Description
|
Description: Description
|
||||||
Version: 1.0.0
|
Version: 2.0.0
|
||||||
Tested up to: 5.4
|
Tested up to: 6.7
|
||||||
Requires PHP: 5.6
|
Requires PHP: 8.1
|
||||||
License: GNU General Public License v2 or later
|
License: GNU General Public License v2 or later
|
||||||
License URI: LICENSE
|
License URI: LICENSE
|
||||||
Text Domain: cyywordpress
|
Text Domain: cyywordpress
|
||||||
|
|||||||
+161
-51
@@ -2,66 +2,176 @@
|
|||||||
/**
|
/**
|
||||||
* Template part for displaying posts
|
* Template part for displaying posts
|
||||||
*
|
*
|
||||||
|
* Modernised card layout:
|
||||||
|
* - 16:9 featured image with lazy-load
|
||||||
|
* - Category colour badges
|
||||||
|
* - Relative publish time + hover absolute time
|
||||||
|
* - Reading-time estimate
|
||||||
|
* - Animated "Continue reading" button
|
||||||
|
* - Skeleton loading CSS hook `.card--loading`
|
||||||
|
*
|
||||||
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/
|
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/
|
||||||
*
|
*
|
||||||
* @package CyyWordpress
|
* @package CyyWordpress
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Estimate reading time (200 wpm average).
|
||||||
|
$word_count = str_word_count( wp_strip_all_tags( get_the_content() ) );
|
||||||
|
$reading_mins = max( 1, (int) ceil( $word_count / 200 ) );
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div id="post-<?php the_ID(); ?>" class="card" style=" margin-bottom: 25px;">
|
<article id="post-<?php the_ID(); ?>" <?php post_class( 'post-card card' ); ?>>
|
||||||
<div class="card-content">
|
|
||||||
<div class="media">
|
|
||||||
<div class="media-content">
|
|
||||||
|
|
||||||
<?php
|
<?php if ( has_post_thumbnail() ) : ?>
|
||||||
if ( is_singular() ) :
|
<div class="post-card__image card-image">
|
||||||
the_title( '<p class="title is-4">', '</p>' );
|
<figure class="image is-16by9">
|
||||||
else :
|
<?php
|
||||||
the_title( '<p class="title is-4"><a href="' . esc_url( get_permalink() ) . '" rel="bookmark">', '</a></p>' );
|
the_post_thumbnail(
|
||||||
endif;
|
'card-thumbnail',
|
||||||
|
array(
|
||||||
|
'class' => 'post-card__thumb',
|
||||||
|
'loading' => 'lazy',
|
||||||
|
'decoding'=> 'async',
|
||||||
|
'alt' => the_title_attribute( array( 'echo' => false ) ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
<?php else : ?>
|
||||||
|
<div class="post-card__image post-card__image--placeholder card-image" aria-hidden="true">
|
||||||
|
<figure class="image is-16by9">
|
||||||
|
<div class="post-card__gradient-placeholder">
|
||||||
|
<?php
|
||||||
|
$cats = get_the_category();
|
||||||
|
echo '<span class="post-card__category-initial">'
|
||||||
|
. esc_html( $cats ? mb_substr( $cats[0]->name, 0, 1 ) : mb_substr( get_bloginfo( 'name' ), 0, 1 ) )
|
||||||
|
. '</span>';
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
if ( 'post' === get_post_type() ) :
|
<div class="card-content">
|
||||||
?>
|
|
||||||
<p class="subtitle is-6">
|
|
||||||
<?php
|
|
||||||
cyywordpress_posted_on();
|
|
||||||
cyywordpress_posted_by();
|
|
||||||
?>
|
|
||||||
</p><!-- .entry-meta -->
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content">
|
<!-- Category badges -->
|
||||||
<?php
|
<?php
|
||||||
the_content(
|
$categories = get_the_category();
|
||||||
sprintf(
|
if ( $categories ) :
|
||||||
wp_kses(
|
echo '<div class="post-card__categories">';
|
||||||
/* translators: %s: Name of current post. Only visible to screen readers */
|
foreach ( array_slice( $categories, 0, 3 ) as $cat ) {
|
||||||
// __( 'Continue reading<span class="screen-reader-text"> "%s"</span>', 'cyywordpress' ),
|
printf(
|
||||||
__( '继续阅读 <span class="screen-reader-text"> "%s"</span>', 'cyywordpress' ),
|
'<a class="post-card__category-badge tag is-link is-light" href="%s">%s</a>',
|
||||||
array(
|
esc_url( get_category_link( $cat->term_id ) ),
|
||||||
'span' => array(
|
esc_html( $cat->name )
|
||||||
'class' => array(),
|
);
|
||||||
),
|
}
|
||||||
)
|
echo '</div>';
|
||||||
),
|
endif;
|
||||||
wp_kses_post( get_the_title() )
|
?>
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
wp_link_pages(
|
<!-- Title -->
|
||||||
array(
|
<?php
|
||||||
'before' => '<div class="page-links">' . esc_html__( 'Pages:', 'cyywordpress' ),
|
if ( is_singular() ) :
|
||||||
'after' => '</div>',
|
the_title( '<h1 class="post-card__title title is-4">', '</h1>' );
|
||||||
)
|
else :
|
||||||
);
|
the_title(
|
||||||
?>
|
'<h2 class="post-card__title title is-4"><a href="' . esc_url( get_permalink() ) . '" rel="bookmark">',
|
||||||
|
'</a></h2>'
|
||||||
|
);
|
||||||
|
endif;
|
||||||
|
?>
|
||||||
|
|
||||||
<footer class="entry-footer">
|
<!-- Meta -->
|
||||||
<?php cyywordpress_entry_footer(); ?>
|
<?php if ( 'post' === get_post_type() ) : ?>
|
||||||
</footer><!-- .entry-footer -->
|
<div class="post-card__meta subtitle is-6">
|
||||||
</div>
|
<span class="post-card__date">
|
||||||
</div>
|
<i class="fa-regular fa-calendar" aria-hidden="true"></i>
|
||||||
</div>
|
<time
|
||||||
|
class="entry-date published"
|
||||||
|
datetime="<?php echo esc_attr( get_the_date( DATE_W3C ) ); ?>"
|
||||||
|
title="<?php echo esc_attr( get_the_date() ); ?>"
|
||||||
|
>
|
||||||
|
<?php
|
||||||
|
echo esc_html(
|
||||||
|
sprintf(
|
||||||
|
/* translators: %s: human-readable time diff */
|
||||||
|
__( '%s前', 'cyywordpress' ),
|
||||||
|
human_time_diff( (int) get_the_time( 'U' ), time() )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</time>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="post-card__author">
|
||||||
|
<i class="fa-regular fa-user" aria-hidden="true"></i>
|
||||||
|
<a href="<?php echo esc_url( get_author_posts_url( (int) get_the_author_meta( 'ID' ) ) ); ?>">
|
||||||
|
<?php echo esc_html( get_the_author() ); ?>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="post-card__reading-time">
|
||||||
|
<i class="fa-regular fa-clock" aria-hidden="true"></i>
|
||||||
|
<?php
|
||||||
|
printf(
|
||||||
|
/* translators: %d: reading time in minutes */
|
||||||
|
esc_html__( '约 %d 分钟', 'cyywordpress' ),
|
||||||
|
(int) $reading_mins
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</span>
|
||||||
|
</div><!-- .post-card__meta -->
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Content / excerpt -->
|
||||||
|
<div class="post-card__content content">
|
||||||
|
<?php
|
||||||
|
if ( is_singular() ) :
|
||||||
|
the_content(
|
||||||
|
sprintf(
|
||||||
|
wp_kses(
|
||||||
|
/* translators: %s: post title (screen-reader only) */
|
||||||
|
__( '继续阅读<span class="screen-reader-text"> "%s"</span>', 'cyywordpress' ),
|
||||||
|
array( 'span' => array( 'class' => array() ) )
|
||||||
|
),
|
||||||
|
wp_kses_post( get_the_title() )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_link_pages(
|
||||||
|
array(
|
||||||
|
'before' => '<div class="page-links">' . esc_html__( '页面:', 'cyywordpress' ),
|
||||||
|
'after' => '</div>',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
else :
|
||||||
|
the_excerpt();
|
||||||
|
endif;
|
||||||
|
?>
|
||||||
|
</div><!-- .post-card__content -->
|
||||||
|
|
||||||
|
<!-- Footer: tags + read-more -->
|
||||||
|
<?php if ( ! is_singular() ) : ?>
|
||||||
|
<div class="post-card__footer">
|
||||||
|
<?php cyywordpress_entry_footer(); ?>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="post-card__read-more button is-link is-light is-small"
|
||||||
|
href="<?php echo esc_url( get_permalink() ); ?>"
|
||||||
|
rel="bookmark"
|
||||||
|
aria-label="<?php echo esc_attr( sprintf( __( '继续阅读 %s', 'cyywordpress' ), get_the_title() ) ); ?>"
|
||||||
|
>
|
||||||
|
<?php esc_html_e( '继续阅读', 'cyywordpress' ); ?>
|
||||||
|
<span class="post-card__read-more-arrow" aria-hidden="true"> →</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<?php else : ?>
|
||||||
|
<footer class="entry-footer">
|
||||||
|
<?php cyywordpress_entry_footer(); ?>
|
||||||
|
</footer>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
</div><!-- .card-content -->
|
||||||
|
</article><!-- #post-<?php the_ID(); ?> -->
|
||||||
|
|||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://schemas.wp.org/trunk/theme.json",
|
||||||
|
"version": 3,
|
||||||
|
"settings": {
|
||||||
|
"color": {
|
||||||
|
"defaultPalette": false,
|
||||||
|
"palette": [
|
||||||
|
{ "name": "Primary", "slug": "primary", "color": "#00d1b2" },
|
||||||
|
{ "name": "Primary Dark", "slug": "primary-dark", "color": "#00a896" },
|
||||||
|
{ "name": "Dark", "slug": "dark", "color": "#363636" },
|
||||||
|
{ "name": "Light", "slug": "light", "color": "#f5f5f5" },
|
||||||
|
{ "name": "White", "slug": "white", "color": "#ffffff" },
|
||||||
|
{ "name": "Danger", "slug": "danger", "color": "#f14668" },
|
||||||
|
{ "name": "Warning", "slug": "warning", "color": "#ffe08a" },
|
||||||
|
{ "name": "Info", "slug": "info", "color": "#3e8ed0" },
|
||||||
|
{ "name": "Success", "slug": "success", "color": "#48c78e" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"typography": {
|
||||||
|
"fontFamilies": [
|
||||||
|
{
|
||||||
|
"name": "Noto Sans SC",
|
||||||
|
"slug": "noto-sans-sc",
|
||||||
|
"fontFamily": "'Noto Sans SC', sans-serif",
|
||||||
|
"fontFace": [
|
||||||
|
{
|
||||||
|
"src": [ "file:./assets/fonts/NotoSansSC-Regular.woff2" ],
|
||||||
|
"fontWeight": "400",
|
||||||
|
"fontStyle": "normal",
|
||||||
|
"fontDisplay": "swap"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": [ "file:./assets/fonts/NotoSansSC-Bold.woff2" ],
|
||||||
|
"fontWeight": "700",
|
||||||
|
"fontStyle": "normal",
|
||||||
|
"fontDisplay": "swap"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "System UI",
|
||||||
|
"slug": "system-ui",
|
||||||
|
"fontFamily": "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fontSizes": [
|
||||||
|
{ "name": "Small", "slug": "small", "size": "0.875rem", "fluid": false },
|
||||||
|
{ "name": "Normal", "slug": "normal", "size": "1rem", "fluid": false },
|
||||||
|
{ "name": "Medium", "slug": "medium", "size": "1.25rem", "fluid": false },
|
||||||
|
{ "name": "Large", "slug": "large", "size": "1.5rem", "fluid": { "min": "1.25rem", "max": "1.5rem" } },
|
||||||
|
{ "name": "X-Large", "slug": "x-large", "size": "2rem", "fluid": { "min": "1.5rem", "max": "2rem" } },
|
||||||
|
{ "name": "XX-Large","slug": "xx-large","size": "2.5rem", "fluid": { "min": "1.75rem", "max": "2.5rem" } }
|
||||||
|
],
|
||||||
|
"lineHeight": true,
|
||||||
|
"letterSpacing": true
|
||||||
|
},
|
||||||
|
"layout": {
|
||||||
|
"contentSize": "760px",
|
||||||
|
"wideSize": "1200px"
|
||||||
|
},
|
||||||
|
"spacing": {
|
||||||
|
"spacingScale": {
|
||||||
|
"operator": "*",
|
||||||
|
"increment": 1.5,
|
||||||
|
"steps": 7,
|
||||||
|
"mediumStep": 1.5,
|
||||||
|
"unit": "rem"
|
||||||
|
},
|
||||||
|
"spacingSizes": [
|
||||||
|
{ "name": "1", "slug": "10", "size": "0.25rem" },
|
||||||
|
{ "name": "2", "slug": "20", "size": "0.5rem" },
|
||||||
|
{ "name": "3", "slug": "30", "size": "1rem" },
|
||||||
|
{ "name": "4", "slug": "40", "size": "1.5rem" },
|
||||||
|
{ "name": "5", "slug": "50", "size": "2rem" },
|
||||||
|
{ "name": "6", "slug": "60", "size": "3rem" },
|
||||||
|
{ "name": "7", "slug": "70", "size": "4.5rem" }
|
||||||
|
],
|
||||||
|
"padding": true,
|
||||||
|
"margin": true,
|
||||||
|
"blockGap": true
|
||||||
|
},
|
||||||
|
"border": {
|
||||||
|
"color": true,
|
||||||
|
"radius": true,
|
||||||
|
"style": true,
|
||||||
|
"width": true
|
||||||
|
},
|
||||||
|
"shadow": {
|
||||||
|
"defaultPresets": false,
|
||||||
|
"presets": [
|
||||||
|
{ "name": "Small", "slug": "sm", "shadow": "0 1px 3px rgba(0,0,0,.1)" },
|
||||||
|
{ "name": "Medium", "slug": "md", "shadow": "0 4px 6px rgba(0,0,0,.1)" },
|
||||||
|
{ "name": "Large", "slug": "lg", "shadow": "0 10px 20px rgba(0,0,0,.12)" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"colorBackground": "#ffffff",
|
||||||
|
"colorText": "#363636",
|
||||||
|
"colorSurface": "#f5f5f5",
|
||||||
|
"colorBorder": "#dbdbdb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"styles": {
|
||||||
|
"color": {
|
||||||
|
"background": "var(--wp--preset--color--white)",
|
||||||
|
"text": "var(--wp--preset--color--dark)"
|
||||||
|
},
|
||||||
|
"typography": {
|
||||||
|
"fontFamily": "var(--wp--preset--font-family--noto-sans-sc)",
|
||||||
|
"fontSize": "var(--wp--preset--font-size--normal)",
|
||||||
|
"lineHeight": "1.7"
|
||||||
|
},
|
||||||
|
"spacing": {
|
||||||
|
"blockGap": "var(--wp--preset--spacing--40)"
|
||||||
|
},
|
||||||
|
"elements": {
|
||||||
|
"link": {
|
||||||
|
"color": { "text": "var(--wp--preset--color--primary)" },
|
||||||
|
":hover": { "color": { "text": "var(--wp--preset--color--primary-dark)" } }
|
||||||
|
},
|
||||||
|
"h1": { "typography": { "fontSize": "var(--wp--preset--font-size--xx-large)", "lineHeight": "1.2" } },
|
||||||
|
"h2": { "typography": { "fontSize": "var(--wp--preset--font-size--x-large)", "lineHeight": "1.3" } },
|
||||||
|
"h3": { "typography": { "fontSize": "var(--wp--preset--font-size--large)", "lineHeight": "1.4" } },
|
||||||
|
"h4": { "typography": { "fontSize": "var(--wp--preset--font-size--medium)", "lineHeight": "1.5" } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"outDir": "./assets/js",
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"],
|
||||||
|
"@scss/*": ["scss/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"],
|
||||||
|
"exclude": ["node_modules", "vendor", "assets"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vite config for CyyWordpress theme.
|
||||||
|
*
|
||||||
|
* Outputs pre-hashed assets to assets/js/ and assets/css/.
|
||||||
|
* Run `npm run dev` → HMR dev server on :3000
|
||||||
|
* Run `npm run build` → production bundles with contenthash
|
||||||
|
*/
|
||||||
|
export default defineConfig( ( { command } ) => ( {
|
||||||
|
root: resolve( __dirname, 'src' ),
|
||||||
|
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@scss': resolve( __dirname, 'scss' ),
|
||||||
|
'@': resolve( __dirname, 'src' ),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
// Expose abstracts to every SCSS file without manual imports.
|
||||||
|
additionalData: `@use "@scss/abstracts/variables" as *;
|
||||||
|
@use "@scss/abstracts/mixins" as *;
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
build: {
|
||||||
|
outDir: resolve( __dirname, 'assets' ),
|
||||||
|
emptyOutDir: false,
|
||||||
|
manifest: true,
|
||||||
|
rollupOptions: {
|
||||||
|
input: {
|
||||||
|
main: resolve( __dirname, 'src/ts/main.ts' ),
|
||||||
|
admin: resolve( __dirname, 'src/admin/customizer.ts' ),
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
entryFileNames: 'js/[name].[hash].js',
|
||||||
|
chunkFileNames: 'js/[name].[hash].js',
|
||||||
|
assetFileNames: ( assetInfo ) => {
|
||||||
|
if ( assetInfo.name?.endsWith( '.css' ) ) {
|
||||||
|
return 'css/[name].[hash][extname]';
|
||||||
|
}
|
||||||
|
return 'img/[name].[hash][extname]';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
server: {
|
||||||
|
port: 3000,
|
||||||
|
// Proxy WordPress requests through the local server.
|
||||||
|
// Point WP_HOME to http://localhost:3000 in wp-config.php during dev.
|
||||||
|
cors: true,
|
||||||
|
},
|
||||||
|
} ) );
|
||||||
Reference in New Issue
Block a user