Compare commits
16
Commits
22c35d0f56
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7859d24c71 | ||
|
|
9ad4763f8b | ||
|
|
b9bbf615c9 | ||
|
|
21db4f31a3 | ||
|
|
d0cb20ce59 | ||
|
|
44cf74b6c4 | ||
|
|
935dd74194 | ||
|
|
e31ed29962 | ||
|
|
0613e33411 | ||
|
|
d0dd9fa98a | ||
|
|
2538a07028 | ||
|
|
868c2cc70a | ||
|
|
d4df6d8678 | ||
|
|
81547254fc | ||
|
|
fe52e51bd9 | ||
|
|
cc463c6131 |
@@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
name: Deploy to Dreamhost
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
# Remove "workflow_dispatch" above and uncomment the lines below to enable deploy on push
|
||||||
|
# only do this once you're actually ready to start deploying to Dreamhost
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ !contains(github.event.head_commit.message, '#skipGA') }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Install rsync
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get -y install rsync
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Cache node modules
|
||||||
|
id: cache-npm
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
env:
|
||||||
|
cache-name: cache-node-modules
|
||||||
|
with:
|
||||||
|
node-version: "22"
|
||||||
|
|
||||||
|
- name: Install npm dependencies
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Run build task
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Deploy to Dreamhost
|
||||||
|
uses: SamKirkland/web-deploy@v1
|
||||||
|
with:
|
||||||
|
target-server: iad1-shared-e1-31.dreamhost.com
|
||||||
|
ssh-port: 22
|
||||||
|
remote-user: zarathos
|
||||||
|
private-ssh-key: ${{ secrets.REMOTE_PRIVATE_KEY }}
|
||||||
|
destination-path: work.keithsolomon.net/wp-content/themes/portfolio-2026
|
||||||
|
rsync-options: --archive --verbose --compress --delete-after --human-readable --exclude=.git* --exclude=.git/ --exclude=.vscode/ --exclude=database/ --exclude=.gitignore --exclude=DOCKER_ENV --exclude=docker_tag --exclude=output.log --exclude=vendor/ --exclude=node_modules/ --exclude=notes/ --exclude=docs/
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
name: Deploy to WPEngine
|
|
||||||
on:
|
|
||||||
workflow_dispatch
|
|
||||||
# Remove "workflow_dispatch" above and uncomment the lines below to enable deploy on push
|
|
||||||
# only do this once you're actually ready to start deploying to Flywheel
|
|
||||||
# push:
|
|
||||||
# branches:
|
|
||||||
# - main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: ${{ !contains(github.event.head_commit.message, '#skipGA') }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Get Composer Cache Directory
|
|
||||||
id: composer-cache
|
|
||||||
run: |
|
|
||||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: ${{ steps.composer-cache.outputs.dir }}
|
|
||||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-composer-
|
|
||||||
|
|
||||||
- name: Install PHP Composer
|
|
||||||
uses: php-actions/composer@v6
|
|
||||||
with:
|
|
||||||
php_version: "8.2"
|
|
||||||
|
|
||||||
- name: Cache node modules
|
|
||||||
id: cache-npm
|
|
||||||
uses: actions/cache@v3
|
|
||||||
env:
|
|
||||||
cache-name: cache-node-modules
|
|
||||||
with:
|
|
||||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
|
||||||
path: ~/.npm
|
|
||||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
|
||||||
${{ runner.os }}-build-
|
|
||||||
${{ runner.os }}-
|
|
||||||
|
|
||||||
- name: Install npm dependencies
|
|
||||||
run: npm install
|
|
||||||
|
|
||||||
- name: Run build task
|
|
||||||
run: npm run build
|
|
||||||
|
|
||||||
- name: Clean up node modules (not needed to deploy)
|
|
||||||
run: rm -rf node_modules
|
|
||||||
|
|
||||||
- name: Deploy to WPE
|
|
||||||
uses: wpengine/github-action-wpe-site-deploy@v3
|
|
||||||
with:
|
|
||||||
WPE_SSHG_KEY_PRIVATE: ${{ secrets.WPE_SSHG_KEY_PRIVATE }}
|
|
||||||
WPE_ENV: <change-me>
|
|
||||||
FLAGS: '-azvr --inplace --delete --exclude=".*"'
|
|
||||||
REMOTE_PATH: "wp-content/themes/<change-me>"
|
|
||||||
+32
-179
@@ -57,27 +57,6 @@
|
|||||||
"append": "",
|
"append": "",
|
||||||
"maxlength": ""
|
"maxlength": ""
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"key": "field_684ad36ebaf40",
|
|
||||||
"label": "Fax",
|
|
||||||
"name": "fax",
|
|
||||||
"aria-label": "",
|
|
||||||
"type": "text",
|
|
||||||
"instructions": "",
|
|
||||||
"required": 0,
|
|
||||||
"conditional_logic": 0,
|
|
||||||
"wrapper": {
|
|
||||||
"width": "",
|
|
||||||
"class": "",
|
|
||||||
"id": ""
|
|
||||||
},
|
|
||||||
"default_value": "",
|
|
||||||
"maxlength": "",
|
|
||||||
"allow_in_bindings": 1,
|
|
||||||
"placeholder": "",
|
|
||||||
"prepend": "",
|
|
||||||
"append": ""
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "field_5fd3e0437bff0",
|
"key": "field_5fd3e0437bff0",
|
||||||
"label": "Address",
|
"label": "Address",
|
||||||
@@ -97,26 +76,6 @@
|
|||||||
"maxlength": "",
|
"maxlength": "",
|
||||||
"rows": 3,
|
"rows": 3,
|
||||||
"new_lines": "br"
|
"new_lines": "br"
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "field_60381dcb082b5",
|
|
||||||
"label": "Hours",
|
|
||||||
"name": "hours",
|
|
||||||
"aria-label": "",
|
|
||||||
"type": "textarea",
|
|
||||||
"instructions": "",
|
|
||||||
"required": 0,
|
|
||||||
"conditional_logic": 0,
|
|
||||||
"wrapper": {
|
|
||||||
"width": "",
|
|
||||||
"class": "",
|
|
||||||
"id": ""
|
|
||||||
},
|
|
||||||
"default_value": "",
|
|
||||||
"placeholder": "",
|
|
||||||
"maxlength": "",
|
|
||||||
"rows": 3,
|
|
||||||
"new_lines": "br"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -125,8 +84,8 @@
|
|||||||
"label": "Social Media",
|
"label": "Social Media",
|
||||||
"name": "social_media",
|
"name": "social_media",
|
||||||
"aria-label": "",
|
"aria-label": "",
|
||||||
"type": "group",
|
"type": "repeater",
|
||||||
"instructions": "",
|
"instructions": "Add social media links. The service icon will be auto-detected from the URL domain.",
|
||||||
"required": 0,
|
"required": 0,
|
||||||
"conditional_logic": 0,
|
"conditional_logic": 0,
|
||||||
"wrapper": {
|
"wrapper": {
|
||||||
@@ -134,105 +93,19 @@
|
|||||||
"class": "",
|
"class": "",
|
||||||
"id": ""
|
"id": ""
|
||||||
},
|
},
|
||||||
"layout": "row",
|
"collapsed": "",
|
||||||
|
"min": 0,
|
||||||
|
"max": 0,
|
||||||
|
"layout": "table",
|
||||||
|
"button_label": "Add Social Link",
|
||||||
"sub_fields": [
|
"sub_fields": [
|
||||||
{
|
|
||||||
"key": "field_677bf0e692d3c",
|
|
||||||
"label": "Facebook",
|
|
||||||
"name": "facebook",
|
|
||||||
"aria-label": "",
|
|
||||||
"type": "url",
|
|
||||||
"instructions": "",
|
|
||||||
"required": 0,
|
|
||||||
"conditional_logic": 0,
|
|
||||||
"wrapper": {
|
|
||||||
"width": "",
|
|
||||||
"class": "",
|
|
||||||
"id": ""
|
|
||||||
},
|
|
||||||
"default_value": "",
|
|
||||||
"allow_in_bindings": 1,
|
|
||||||
"placeholder": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "field_677bf0e692d3e",
|
|
||||||
"label": "Instagram",
|
|
||||||
"name": "instagram",
|
|
||||||
"aria-label": "",
|
|
||||||
"type": "url",
|
|
||||||
"instructions": "",
|
|
||||||
"required": 0,
|
|
||||||
"conditional_logic": 0,
|
|
||||||
"wrapper": {
|
|
||||||
"width": "",
|
|
||||||
"class": "",
|
|
||||||
"id": ""
|
|
||||||
},
|
|
||||||
"default_value": "",
|
|
||||||
"allow_in_bindings": 1,
|
|
||||||
"placeholder": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "field_677bf0e692d3d",
|
|
||||||
"label": "X\/Twitter",
|
|
||||||
"name": "twitter",
|
|
||||||
"aria-label": "",
|
|
||||||
"type": "url",
|
|
||||||
"instructions": "",
|
|
||||||
"required": 0,
|
|
||||||
"conditional_logic": 0,
|
|
||||||
"wrapper": {
|
|
||||||
"width": "",
|
|
||||||
"class": "",
|
|
||||||
"id": ""
|
|
||||||
},
|
|
||||||
"default_value": "",
|
|
||||||
"allow_in_bindings": 1,
|
|
||||||
"placeholder": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "field_677bf0e692d41",
|
|
||||||
"label": "LinkedIn",
|
|
||||||
"name": "linkedin",
|
|
||||||
"aria-label": "",
|
|
||||||
"type": "url",
|
|
||||||
"instructions": "",
|
|
||||||
"required": 0,
|
|
||||||
"conditional_logic": 0,
|
|
||||||
"wrapper": {
|
|
||||||
"width": "",
|
|
||||||
"class": "",
|
|
||||||
"id": ""
|
|
||||||
},
|
|
||||||
"default_value": "",
|
|
||||||
"allow_in_bindings": 1,
|
|
||||||
"placeholder": ""
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "field_677bf0e692d40",
|
"key": "field_677bf0e692d40",
|
||||||
"label": "Youtube",
|
"label": "URL",
|
||||||
"name": "youtube",
|
"name": "url",
|
||||||
"aria-label": "",
|
"aria-label": "",
|
||||||
"type": "url",
|
"type": "url",
|
||||||
"instructions": "",
|
"instructions": "Enter the full URL to your social media profile",
|
||||||
"required": 0,
|
|
||||||
"conditional_logic": 0,
|
|
||||||
"wrapper": {
|
|
||||||
"width": "",
|
|
||||||
"class": "",
|
|
||||||
"id": ""
|
|
||||||
},
|
|
||||||
"default_value": "",
|
|
||||||
"allow_in_bindings": 1,
|
|
||||||
"placeholder": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "field_677bf0e692d3f",
|
|
||||||
"label": "Pinterest",
|
|
||||||
"name": "pinterest",
|
|
||||||
"aria-label": "",
|
|
||||||
"type": "url",
|
|
||||||
"instructions": "",
|
|
||||||
"required": 0,
|
"required": 0,
|
||||||
"conditional_logic": 0,
|
"conditional_logic": 0,
|
||||||
"wrapper": {
|
"wrapper": {
|
||||||
@@ -286,6 +159,24 @@
|
|||||||
"max_height": "",
|
"max_height": "",
|
||||||
"max_size": "",
|
"max_size": "",
|
||||||
"mime_types": ""
|
"mime_types": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "field_6a846bd4360da",
|
||||||
|
"label": "Resume Link",
|
||||||
|
"name": "resume_link",
|
||||||
|
"aria-label": "",
|
||||||
|
"type": "url",
|
||||||
|
"instructions": "",
|
||||||
|
"required": 0,
|
||||||
|
"conditional_logic": 0,
|
||||||
|
"wrapper": {
|
||||||
|
"width": "",
|
||||||
|
"class": "",
|
||||||
|
"id": ""
|
||||||
|
},
|
||||||
|
"default_value": "",
|
||||||
|
"allow_in_bindings": 0,
|
||||||
|
"placeholder": ""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -330,26 +221,6 @@
|
|||||||
"max_size": "",
|
"max_size": "",
|
||||||
"mime_types": ""
|
"mime_types": ""
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"key": "field_64761ea8cb0dd",
|
|
||||||
"label": "Footer Description",
|
|
||||||
"name": "footer_description",
|
|
||||||
"aria-label": "",
|
|
||||||
"type": "wysiwyg",
|
|
||||||
"instructions": "",
|
|
||||||
"required": 0,
|
|
||||||
"conditional_logic": 0,
|
|
||||||
"wrapper": {
|
|
||||||
"width": "",
|
|
||||||
"class": "",
|
|
||||||
"id": ""
|
|
||||||
},
|
|
||||||
"default_value": "",
|
|
||||||
"tabs": "all",
|
|
||||||
"toolbar": "full",
|
|
||||||
"media_upload": 1,
|
|
||||||
"delay": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "field_61df3348c3795",
|
"key": "field_61df3348c3795",
|
||||||
"label": "Copyright Text",
|
"label": "Copyright Text",
|
||||||
@@ -370,27 +241,6 @@
|
|||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"prepend": "",
|
"prepend": "",
|
||||||
"append": ""
|
"append": ""
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "field_684af923d5f68",
|
|
||||||
"label": "Credit Text",
|
|
||||||
"name": "credit_text",
|
|
||||||
"aria-label": "",
|
|
||||||
"type": "text",
|
|
||||||
"instructions": "",
|
|
||||||
"required": 0,
|
|
||||||
"conditional_logic": 0,
|
|
||||||
"wrapper": {
|
|
||||||
"width": "",
|
|
||||||
"class": "",
|
|
||||||
"id": ""
|
|
||||||
},
|
|
||||||
"default_value": "Web Design & Development by <a href=\"https:\/\/vincentdesign.ca\/\"><strong>Vincent Design<\/strong><\/a>",
|
|
||||||
"maxlength": "",
|
|
||||||
"allow_in_bindings": 1,
|
|
||||||
"placeholder": "",
|
|
||||||
"prepend": "",
|
|
||||||
"append": ""
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -455,5 +305,8 @@
|
|||||||
"active": true,
|
"active": true,
|
||||||
"description": "",
|
"description": "",
|
||||||
"show_in_rest": 0,
|
"show_in_rest": 0,
|
||||||
"modified": 1749744328
|
"display_title": "",
|
||||||
|
"allow_ai_access": false,
|
||||||
|
"ai_description": "",
|
||||||
|
"modified": 1787502285
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,32 @@
|
|||||||
"key": "group_60bfdb328901d",
|
"key": "group_60bfdb328901d",
|
||||||
"title": "Homepage Hero",
|
"title": "Homepage Hero",
|
||||||
"fields": [
|
"fields": [
|
||||||
|
{
|
||||||
|
"key": "field_6a84d6008c2b1",
|
||||||
|
"label": "Hero Image",
|
||||||
|
"name": "hero_image",
|
||||||
|
"aria-label": "",
|
||||||
|
"type": "image",
|
||||||
|
"instructions": "",
|
||||||
|
"required": 0,
|
||||||
|
"conditional_logic": 0,
|
||||||
|
"wrapper": {
|
||||||
|
"width": "",
|
||||||
|
"class": "",
|
||||||
|
"id": ""
|
||||||
|
},
|
||||||
|
"return_format": "array",
|
||||||
|
"library": "all",
|
||||||
|
"min_width": "",
|
||||||
|
"min_height": "",
|
||||||
|
"min_size": "",
|
||||||
|
"max_width": "",
|
||||||
|
"max_height": "",
|
||||||
|
"max_size": "",
|
||||||
|
"mime_types": "",
|
||||||
|
"allow_in_bindings": 0,
|
||||||
|
"preview_size": "medium"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "field_60bfdb328cfae",
|
"key": "field_60bfdb328cfae",
|
||||||
"label": "Heading",
|
"label": "Heading",
|
||||||
@@ -102,5 +128,8 @@
|
|||||||
"active": true,
|
"active": true,
|
||||||
"description": "",
|
"description": "",
|
||||||
"show_in_rest": 0,
|
"show_in_rest": 0,
|
||||||
"modified": 1742332828
|
"display_title": "",
|
||||||
|
"allow_ai_access": false,
|
||||||
|
"ai_description": "",
|
||||||
|
"modified": 1787090455
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Project Type Taxonomy Archive Template (plugin override)
|
||||||
|
*
|
||||||
|
* The projects-portfolio plugin's template loader looks for this filename
|
||||||
|
* (archive-project-type.php) via locate_template(); when present it is used
|
||||||
|
* in place of the plugin's bundled template. We mirror the pattern used by
|
||||||
|
* the post-type archive and the Recent Projects block: a flex-row head
|
||||||
|
* with the term title on the left and an "All Projects" link on the right,
|
||||||
|
* a divider, then the project card grid.
|
||||||
|
*
|
||||||
|
* @package KsPortfolio
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace KsPortfolio;
|
||||||
|
|
||||||
|
get_header();
|
||||||
|
|
||||||
|
$queried = get_queried_object();
|
||||||
|
$term_name = $queried && ! is_wp_error( $queried ) ? $queried->name : __( 'Projects', 'ks-portfolio' );
|
||||||
|
?>
|
||||||
|
<section class="tax-project container mx-auto my-section">
|
||||||
|
<?php if ( have_posts() ) : ?>
|
||||||
|
<div class="tax-project__grid grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
<?php
|
||||||
|
while ( have_posts() ) :
|
||||||
|
the_post();
|
||||||
|
get_template_part( 'views/components/project-card' );
|
||||||
|
endwhile;
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tax-project__pagination">
|
||||||
|
<?php
|
||||||
|
the_posts_pagination(
|
||||||
|
array(
|
||||||
|
'mid_size' => 2,
|
||||||
|
'prev_text' => '« ' . esc_html__( 'Previous', 'ks-portfolio' ),
|
||||||
|
'next_text' => esc_html__( 'Next', 'ks-portfolio' ) . ' »',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
<?php else : ?>
|
||||||
|
<div class="tax-project__empty">
|
||||||
|
<h2 class="tax-project__empty-title"><?php echo esc_html__( 'Nothing here yet…', 'ks-portfolio' ); ?></h2>
|
||||||
|
<p class="tax-project__empty-desc"><?php echo esc_html__( 'No published projects found in this category.', 'ks-portfolio' ); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
get_footer();
|
||||||
@@ -10,16 +10,16 @@ namespace KsPortfolio;
|
|||||||
get_header();
|
get_header();
|
||||||
|
|
||||||
$hero_intro = get_field( 'archive_intro', (int) get_queried_object_id() );
|
$hero_intro = get_field( 'archive_intro', (int) get_queried_object_id() );
|
||||||
|
|
||||||
if ( ! $hero_intro ) {
|
if ( ! $hero_intro ) {
|
||||||
$hero_intro = __( 'A comprehensive catalog of WordPress plugins, custom themes, and open-source contributions developed for scalable enterprise environments. Focused on modern development practices, performant architectures, and secure integrations.', 'ks-portfolio' );
|
$hero_intro = __( 'A comprehensive catalog of WordPress plugins, custom themes, and open-source contributions developed for scalable enterprise environments. Focused on modern development practices, performant architectures, and secure integrations.', 'ks-portfolio' );
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<section class="archive-projects aura-bg">
|
|
||||||
<?php get_template_part( 'views/partials/aura-bg' ); ?>
|
<section class="archive-projects">
|
||||||
<div class="aura-bg__content">
|
|
||||||
<div class="container mx-auto my-section">
|
<div class="container mx-auto my-section">
|
||||||
<header class="archive-projects__head">
|
<header class="archive-projects__head">
|
||||||
<h1 class="archive-projects__title"><?php echo esc_html__( 'Project Archive', 'ks-portfolio' ); ?></h1>
|
<h1 class="archive-projects__title"><?php echo esc_html__( 'Projects', 'ks-portfolio' ); ?></h1>
|
||||||
<p class="archive-projects__intro"><?php echo esc_html( $hero_intro ); ?></p>
|
<p class="archive-projects__intro"><?php echo esc_html( $hero_intro ); ?></p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -51,7 +51,6 @@ if ( ! $hero_intro ) {
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
|
|||||||
+62
-15
@@ -25,22 +25,26 @@ $footerNav = ! empty( $locations['footer_navigation'] )
|
|||||||
|
|
||||||
<footer role="contentinfo" class="site-footer">
|
<footer role="contentinfo" class="site-footer">
|
||||||
<div class="container mx-auto py-12">
|
<div class="container mx-auto py-12">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
<div class="flex flex-col-reverse lg:flex-row justify-between gap-8">
|
||||||
<div class="site-footer__brand max-w-[40ch]">
|
<div class="site-footer__brand max-w-[40ch]">
|
||||||
<h2 class="site-footer__title">
|
<h2 class="site-footer__title">
|
||||||
<a href="<?php echo esc_url( home_url( '/' ) ); ?>">
|
<a href="<?php echo esc_url( home_url( '/' ) ); ?>">
|
||||||
<?php if ( $footerLogo ) { ?>
|
<?php echo esc_html( get_bloginfo( 'name' ) ); ?>
|
||||||
<img src="<?php echo esc_url( $footerLogo ); ?>" alt="<?php echo esc_attr( get_bloginfo( 'name' ) ); ?>" />
|
|
||||||
<?php
|
|
||||||
} else {
|
|
||||||
echo esc_html( get_bloginfo( 'name' ) );
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
</a>
|
</a>
|
||||||
</h2>
|
</h2>
|
||||||
<?php if ( $footerDesc ) : ?>
|
|
||||||
<p class="site-footer__desc"><?php echo wp_kses_post( $footerDesc ); ?></p>
|
<div class="social-links">
|
||||||
<?php endif; ?>
|
<?php
|
||||||
|
get_template_part(
|
||||||
|
'views/partials/social-media',
|
||||||
|
null,
|
||||||
|
array(
|
||||||
|
'circle' => false,
|
||||||
|
'classes' => 'social-icons p-0 mr-1',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
@@ -57,16 +61,59 @@ $footerNav = ! empty( $locations['footer_navigation'] )
|
|||||||
|
|
||||||
<?php if ( $footerNav ) : ?>
|
<?php if ( $footerNav ) : ?>
|
||||||
<nav class="site-footer__nav" aria-label="<?php echo esc_attr__( 'Footer navigation', 'ks-portfolio' ); ?>">
|
<nav class="site-footer__nav" aria-label="<?php echo esc_attr__( 'Footer navigation', 'ks-portfolio' ); ?>">
|
||||||
<h3><?php echo esc_html__( 'Navigation', 'ks-portfolio' ); ?></h3>
|
<h3><?php echo esc_html__( 'Quick Links', 'ks-portfolio' ); ?></h3>
|
||||||
<ul>
|
<ul>
|
||||||
<?php foreach ( $footerNav as $item ) : ?>
|
<?php
|
||||||
<li>
|
// Build a tree of parent -> children relationships
|
||||||
<a href="<?php echo esc_url( $item->url ); ?>">
|
$menu_items_by_parent = array();
|
||||||
|
foreach ( $footerNav as $item ) {
|
||||||
|
$parent_id = (int) $item->menu_item_parent;
|
||||||
|
if ( ! isset( $menu_items_by_parent[ $parent_id ] ) ) {
|
||||||
|
$menu_items_by_parent[ $parent_id ] = array();
|
||||||
|
}
|
||||||
|
$menu_items_by_parent[ $parent_id ][] = $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display top-level items (parent = 0)
|
||||||
|
if ( isset( $menu_items_by_parent[0] ) ) {
|
||||||
|
foreach ( $menu_items_by_parent[0] as $item ) :
|
||||||
|
// Check if this is the contact link
|
||||||
|
$is_contact = false;
|
||||||
|
if ( function_exists( 'get_field' ) ) {
|
||||||
|
$contact_link = get_field( 'contact_info.email', 'option' );
|
||||||
|
if ( $contact_link && strpos( $item->url, 'contact' ) !== false ) {
|
||||||
|
$is_contact = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<li<?php echo $is_contact ? ' class="site-footer__nav-contact"' : ''; ?>>
|
||||||
|
<a href="<?php echo esc_url( $item->url ); ?>" class="<?php echo $is_contact ? 'button button-primary' : ''; ?>">
|
||||||
<?php echo esc_html( $item->title ); ?>
|
<?php echo esc_html( $item->title ); ?>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Check for child items
|
||||||
|
$item_id = (int) $item->ID;
|
||||||
|
if ( isset( $menu_items_by_parent[ $item_id ] ) ) {
|
||||||
|
?>
|
||||||
|
<ul class="site-footer__nav-submenu">
|
||||||
|
<?php foreach ( $menu_items_by_parent[ $item_id ] as $child_item ) : ?>
|
||||||
|
<li class="site-footer__nav-submenu-item">
|
||||||
|
<a href="<?php echo esc_url( $child_item->url ); ?>">
|
||||||
|
<?php echo esc_html( $child_item->title ); ?>
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</ul>
|
</ul>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</li>
|
||||||
|
<?php
|
||||||
|
endforeach;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+11
-68
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Front Page Template — Portfolio 2026 home
|
* Front Page Template
|
||||||
*
|
*
|
||||||
* @package KsPortfolio
|
* @package KsPortfolio
|
||||||
*/
|
*/
|
||||||
@@ -8,77 +8,20 @@
|
|||||||
namespace KsPortfolio;
|
namespace KsPortfolio;
|
||||||
|
|
||||||
get_header();
|
get_header();
|
||||||
|
|
||||||
$hero_heading = get_field( 'homepage_hero_heading' );
|
|
||||||
$hero_intro = get_field( 'homepage_hero_intro' );
|
|
||||||
$hero_cta_1 = get_field( 'homepage_hero_cta_1' ); // ACF link fields return shape: URL, title, target.
|
|
||||||
$hero_cta_2 = get_field( 'homepage_hero_cta_2' );
|
|
||||||
?>
|
?>
|
||||||
<section class="home-hero aura-bg">
|
|
||||||
<?php get_template_part( 'views/partials/aura-bg' ); ?>
|
|
||||||
<div class="aura-bg__content">
|
|
||||||
<div class="container mx-auto my-section">
|
|
||||||
<div class="home-hero__inner">
|
|
||||||
<?php if ( $hero_heading ) : ?>
|
|
||||||
<h1 class="home-hero__heading"><?php echo esc_html( $hero_heading ); ?></h1>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if ( $hero_intro ) : ?>
|
|
||||||
<p class="home-hero__intro"><?php echo esc_html( $hero_intro ); ?></p>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<div class="home-hero__actions">
|
|
||||||
<?php if ( ! empty( $hero_cta_1['url'] ) && ! empty( $hero_cta_1['title'] ) ) : ?>
|
|
||||||
<a class="button button--primary" href="<?php echo esc_url( $hero_cta_1['url'] ); ?>"<?php echo ! empty( $hero_cta_1['target'] ) ? ' target="_blank" rel="noopener noreferrer"' : ''; ?>>
|
|
||||||
<?php echo esc_html( $hero_cta_1['title'] ); ?>
|
|
||||||
</a>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if ( ! empty( $hero_cta_2['url'] ) && ! empty( $hero_cta_2['title'] ) ) : ?>
|
|
||||||
<a class="button button--outline" href="<?php echo esc_url( $hero_cta_2['url'] ); ?>"<?php echo ! empty( $hero_cta_2['target'] ) ? ' target="_blank" rel="noopener noreferrer"' : ''; ?>>
|
|
||||||
<?php echo esc_html( $hero_cta_2['title'] ); ?>
|
|
||||||
</a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="home-recent container mx-auto my-section">
|
|
||||||
<header class="home-recent__head">
|
|
||||||
<h2 class="home-recent__title"><?php echo esc_html__( 'Recent Projects', 'ks-portfolio' ); ?></h2>
|
|
||||||
<a class="home-recent__view-all" href="<?php echo esc_url( get_post_type_archive_link( 'projects' ) ); ?>">
|
|
||||||
<?php echo esc_html__( 'View All', 'ks-portfolio' ); ?> →
|
|
||||||
</a>
|
|
||||||
</header>
|
|
||||||
<hr class="home-recent__divider" />
|
|
||||||
|
|
||||||
|
<div class="container my-section lg:my-0 mx-auto">
|
||||||
|
<div>
|
||||||
<?php
|
<?php
|
||||||
$recent = new \WP_Query(
|
// Content block
|
||||||
array(
|
if ( have_posts() ) {
|
||||||
'post_type' => 'projects',
|
while ( have_posts() ) {
|
||||||
'posts_per_page' => 3,
|
the_post();
|
||||||
'no_found_rows' => true,
|
the_content();
|
||||||
)
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
if ( $recent->have_posts() ) :
|
|
||||||
?>
|
|
||||||
<div class="home-recent__grid grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
||||||
<?php
|
|
||||||
while ( $recent->have_posts() ) :
|
|
||||||
$recent->the_post();
|
|
||||||
get_template_part( 'views/components/project-card' );
|
|
||||||
endwhile;
|
|
||||||
wp_reset_postdata();
|
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
<?php else : ?>
|
|
||||||
<div class="home-recent__empty">
|
|
||||||
<h3><?php echo esc_html__( 'Nothing here yet…', 'ks-portfolio' ); ?></h3>
|
|
||||||
<p><?php echo esc_html__( 'No published projects found.', 'ks-portfolio' ); ?></p>
|
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<?php
|
<?php get_footer(); ?>
|
||||||
get_footer();
|
|
||||||
|
|||||||
+14
-26
@@ -11,6 +11,10 @@ namespace KsPortfolio;
|
|||||||
global $views;
|
global $views;
|
||||||
|
|
||||||
$headerLogo = getFieldValue( 'header.header_logo.url' ) ? getFieldValue( 'header.header_logo.url' ) : get_theme_file_uri( '/static/img/logo.svg' );
|
$headerLogo = getFieldValue( 'header.header_logo.url' ) ? getFieldValue( 'header.header_logo.url' ) : get_theme_file_uri( '/static/img/logo.svg' );
|
||||||
|
$linkResume = getFieldValue( 'header.resume_link' ) ? getFieldValue( 'header.resume_link' ) : '';
|
||||||
|
|
||||||
|
// Expose resume link to the main nav component (used by the mobile menu's footer area).
|
||||||
|
$GLOBALS['ks_portfolio_link_resume'] = $linkResume;
|
||||||
|
|
||||||
// Check conditions for displaying the hero section
|
// Check conditions for displaying the hero section
|
||||||
$showHero = in_array(
|
$showHero = in_array(
|
||||||
@@ -25,30 +29,12 @@ $showHero = in_array(
|
|||||||
),
|
),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
// Carbon Blue project templates render their own <h1> inside the .aura-bg
|
|
||||||
// hero, so suppress the page-hero partial on those templates to avoid
|
|
||||||
// duplicate <h1> elements on the page.
|
|
||||||
$projectTemplates = array( 'front-page', 'archive-projects', 'single-projects' );
|
|
||||||
$isProjectTemplate = in_array( get_page_template_slug( get_the_ID() ), $projectTemplates, true );
|
|
||||||
if ( ! $isProjectTemplate ) {
|
|
||||||
$isProjectTemplate = in_array( get_post_type(), array( 'projects' ), true ) && (
|
|
||||||
is_front_page()
|
|
||||||
|| is_archive( 'projects' )
|
|
||||||
|| is_singular( 'projects' )
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if ( $isProjectTemplate ) {
|
|
||||||
$showHero = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html <?php language_attributes(); ?>>
|
<html <?php language_attributes(); ?>>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title><?php wp_title( '' ); ?></title>
|
|
||||||
<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">
|
||||||
|
|
||||||
@@ -60,22 +46,24 @@ if ( $isProjectTemplate ) {
|
|||||||
<?php echo esc_html__( 'Skip to main content' ); ?>
|
<?php echo esc_html__( 'Skip to main content' ); ?>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<header role="banner" class="site-header bg-secondary flex flex-col items-center justify-start">
|
<?php get_template_part( 'views/partials/aura-bg' ); ?>
|
||||||
<?php get_template_part( 'views/components/nav-aux' ); ?>
|
|
||||||
|
|
||||||
<div class="header__nav-main container py-4 items-center grid gap-x-8 gap-y-0 grid-cols-[auto_1fr_auto] grid-rows-[1fr] justify-between">
|
<header role="banner" class="site-header bg-secondary flex flex-col items-center justify-start">
|
||||||
<a href="<?php echo esc_url( home_url( '/' ) ); ?>" class="site-header__logo block">
|
<div class="header__nav-main lg:container py-4 items-center flex gap-x-8 gap-y-0 justify-between">
|
||||||
<img class="site-header__logo-img" src="<?php echo esc_url( $headerLogo ); ?>" alt="<?php echo esc_attr( get_bloginfo( 'name' ) ); ?> logo" />
|
<a href="<?php echo esc_url( home_url( '/' ) ); ?>" class="site-header__logo m-0 p-0 flex gap-x-4 items-center justify-start text-white! text-45px font-bold leading-none hover:underline">
|
||||||
|
<img class="site-header__logo-img aspect-square h-full w-full max-h-12 max-w-12" src="<?php echo esc_url( $headerLogo ); ?>" alt="<?php echo esc_attr( get_bloginfo( 'name' ) ); ?> logo" /> Keith Solomon
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<div class="headRight lg:flex lg:items-center lg:gap-x-4 lg:gap-y-0 lg:justify-end">
|
||||||
<?php get_template_part( 'views/components/nav-main' ); ?>
|
<?php get_template_part( 'views/components/nav-main' ); ?>
|
||||||
|
|
||||||
<div class="site-header__aux">
|
<div class="site-header__aux hidden lg:flex">
|
||||||
<a class="button button--outline" href="<?php echo esc_url( home_url( '/cv/' ) ); ?>">
|
<a class="button button--outline" href="<?php echo esc_url( $linkResume ); ?>">
|
||||||
<?php echo esc_html__( 'Download CV', 'ks-portfolio' ); ?>
|
<?php echo esc_html__( 'Resume', 'ks-portfolio' ); ?>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main id="maincontent" class="overflow-hidden min-h-[78dvh]">
|
<main id="maincontent" class="overflow-hidden min-h-[78dvh]">
|
||||||
|
|||||||
@@ -46,7 +46,11 @@ class Breadcrumbs {
|
|||||||
// Static Page - add the parent pages if any, and the page title
|
// Static Page - add the parent pages if any, and the page title
|
||||||
$breadcrumbs = array_merge( $breadcrumbs, self::getStaticPageBreadcrumbs( $post ) );
|
$breadcrumbs = array_merge( $breadcrumbs, self::getStaticPageBreadcrumbs( $post ) );
|
||||||
} elseif ( is_category() || is_tag() || is_tax() ) {
|
} elseif ( is_category() || is_tag() || is_tax() ) {
|
||||||
// Taxonomy Archive - add the taxonomy term name
|
// Taxonomy Archive - if this is a project-type term, surface the
|
||||||
|
// Projects archive as a clickable parent in the trail.
|
||||||
|
if ( is_tax( 'project-type' ) ) {
|
||||||
|
$breadcrumbs[] = self::getProjectsArchiveLinkBreadcrumb();
|
||||||
|
}
|
||||||
$breadcrumbs[] = self::getTaxonomyArchiveBreadcrumb();
|
$breadcrumbs[] = self::getTaxonomyArchiveBreadcrumb();
|
||||||
} elseif ( is_post_type_archive() ) {
|
} elseif ( is_post_type_archive() ) {
|
||||||
// Post Type Archive - add the post type name
|
// Post Type Archive - add the post type name
|
||||||
@@ -205,6 +209,24 @@ class Breadcrumbs {
|
|||||||
return $postType ? array( 'label' => $postType->labels->name ) : array();
|
return $postType ? array( 'label' => $postType->labels->name ) : array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Generates a clickable Projects archive breadcrumb.
|
||||||
|
*
|
||||||
|
* Used in the taxonomy archive trail so users have a working "back to
|
||||||
|
* all projects" link in the breadcrumb. Distinct from
|
||||||
|
* getPostTypeArchiveBreadcrumb(), which omits the URL because on the
|
||||||
|
* post-type archive itself that crumb is the active leaf and shouldn't
|
||||||
|
* link to itself.
|
||||||
|
*
|
||||||
|
* @return array Breadcrumb data with both label and url.
|
||||||
|
*/
|
||||||
|
private static function getProjectsArchiveLinkBreadcrumb() {
|
||||||
|
$url = get_post_type_archive_link( 'projects' );
|
||||||
|
return array(
|
||||||
|
'url' => $url ? $url : '',
|
||||||
|
'label' => __( 'Projects', 'ks-portfolio' ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** Generates breadcrumbs for date-based archives.
|
/** Generates breadcrumbs for date-based archives.
|
||||||
*
|
*
|
||||||
* This method is responsible for creating breadcrumb navigation
|
* This method is responsible for creating breadcrumb navigation
|
||||||
|
|||||||
@@ -139,4 +139,57 @@ class Projects {
|
|||||||
}
|
}
|
||||||
return number_format( $value );
|
return number_format( $value );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the project is configured as a website rather than a git repo.
|
||||||
|
*
|
||||||
|
* @param int $post_id Project post ID.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function is_website( int $post_id ): bool {
|
||||||
|
if ( ! self::is_plugin_active() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (bool) projects_portfolio_is_website( $post_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public URL for the project's website (or empty string).
|
||||||
|
*
|
||||||
|
* @param int $post_id Project post ID.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function get_site_url( int $post_id ): string {
|
||||||
|
if ( ! self::is_plugin_active() ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return (string) projects_portfolio_get_site_url( $post_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform label for the project's website (or empty string).
|
||||||
|
*
|
||||||
|
* @param int $post_id Project post ID.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function get_site_platform( int $post_id ): string {
|
||||||
|
if ( ! self::is_plugin_active() ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return (string) projects_portfolio_get_site_platform( $post_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Screenshot attachment URL for the project's website (or empty string).
|
||||||
|
*
|
||||||
|
* @param int $post_id Project post ID.
|
||||||
|
* @param string $size Registered image size.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function get_site_screenshot_url( int $post_id, string $size = 'large' ): string {
|
||||||
|
if ( ! self::is_plugin_active() ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return (string) projects_portfolio_get_site_screenshot_url( $post_id, $size );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-3
@@ -65,16 +65,20 @@ add_filter(
|
|||||||
/**
|
/**
|
||||||
* Checks if the page should render a page header.
|
* Checks if the page should render a page header.
|
||||||
*
|
*
|
||||||
|
* Returns true only when the page heading ACF field is explicitly set to
|
||||||
|
* 'default'. When 'none' is selected, no page header should render (the
|
||||||
|
* page instead relies on its own hero block / template hero).
|
||||||
|
*
|
||||||
* @return bool true if page header should be rendered, false otherwise
|
* @return bool true if page header should be rendered, false otherwise
|
||||||
*/
|
*/
|
||||||
function hasPageHeader() {
|
function hasPageHeader() {
|
||||||
global $post;
|
global $post;
|
||||||
|
|
||||||
if ( get_field( 'hero_style' ) !== 'none' ) {
|
if ( get_field( 'hero_style' ) === 'default' ) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create the Owner role.
|
/** Create the Owner role.
|
||||||
@@ -150,6 +154,8 @@ function getTheTitle() {
|
|||||||
if ( is_home() || is_single() ) {
|
if ( is_home() || is_single() ) {
|
||||||
$title = get_the_title( get_option( 'page_for_posts', true ) );
|
$title = get_the_title( get_option( 'page_for_posts', true ) );
|
||||||
} elseif ( is_archive() ) {
|
} elseif ( is_archive() ) {
|
||||||
|
// get_the_archive_title() is filtered below to drop the
|
||||||
|
// "Project Type:" / "Category:" prefix — see stripArchiveTitlePrefix().
|
||||||
$title = get_the_archive_title();
|
$title = get_the_archive_title();
|
||||||
} elseif ( is_search() ) {
|
} elseif ( is_search() ) {
|
||||||
$title = sprintf(
|
$title = sprintf(
|
||||||
@@ -166,6 +172,26 @@ function getTheTitle() {
|
|||||||
return $title;
|
return $title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Strip the "Project Type:" / "Category:" / "Tag:" prefix from archive titles.
|
||||||
|
*
|
||||||
|
* WordPress's get_the_archive_title() returns plain text with the taxonomy
|
||||||
|
* or post-type label concatenated before a colon (e.g. "Project Type: Plugins",
|
||||||
|
* "Category: WordPress", "Tag: php"). The site's hero partial uses the raw
|
||||||
|
* archive title as its <h1>, and templates render their own context-appropriate
|
||||||
|
* headers, so the prefix is redundant and visually noisy.
|
||||||
|
*
|
||||||
|
* @param string $title The full archive title, including prefix.
|
||||||
|
* @return string The title with the prefix removed.
|
||||||
|
*/
|
||||||
|
function stripArchiveTitlePrefix( $title ) {
|
||||||
|
// Drop the "<Label>: " prefix WordPress prepends. The label may be wrapped
|
||||||
|
// in a <span> on some archive types (date archives) — strip that too.
|
||||||
|
$title = (string) $title;
|
||||||
|
$title = preg_replace( '/^\s*(?:<[^>]+>\s*)*[^:<]+:\s*/u', '', $title );
|
||||||
|
return $title;
|
||||||
|
}
|
||||||
|
add_filter( 'get_the_archive_title', __NAMESPACE__ . '\\stripArchiveTitlePrefix' );
|
||||||
|
|
||||||
/** Wraps iframes and embed elements in a div with a specific class.
|
/** Wraps iframes and embed elements in a div with a specific class.
|
||||||
*
|
*
|
||||||
* This function searches for iframe and embed elements within the provided
|
* This function searches for iframe and embed elements within the provided
|
||||||
@@ -194,6 +220,26 @@ function divWrapper( $content ) {
|
|||||||
|
|
||||||
add_filter( 'the_content', __NAMESPACE__ . '\\divWrapper' );
|
add_filter( 'the_content', __NAMESPACE__ . '\\divWrapper' );
|
||||||
|
|
||||||
|
/** Strip the "<Label>: " prefix from the document title.
|
||||||
|
*
|
||||||
|
* The SEO Framework wipes out all `pre_get_document_title` filters at
|
||||||
|
* `template_redirect` priority 20 and composes the title itself. To strip
|
||||||
|
* the archive prefix consistently across <title>, og:title, and twitter:title
|
||||||
|
* (which all derive from TSF's generated archive title list), hook the
|
||||||
|
* archive-title-items filter and return the unprefixed title.
|
||||||
|
*
|
||||||
|
* @param array $items [title, prefix, title_without_prefix].
|
||||||
|
* @return array The same items with the prefix removed from the title.
|
||||||
|
*/
|
||||||
|
function stripArchiveTitleItemsPrefix( $items ) {
|
||||||
|
if ( ! is_array( $items ) || empty( $items[2] ) ) {
|
||||||
|
return $items;
|
||||||
|
}
|
||||||
|
$items[0] = $items[2];
|
||||||
|
return $items;
|
||||||
|
}
|
||||||
|
add_filter( 'the_seo_framework_generated_archive_title_items', __NAMESPACE__ . '\\stripArchiveTitleItemsPrefix' );
|
||||||
|
|
||||||
/** Selectively add sidebar to page.
|
/** Selectively add sidebar to page.
|
||||||
*
|
*
|
||||||
* This function adds a custom field group to the WordPress editor for
|
* This function adds a custom field group to the WordPress editor for
|
||||||
|
|||||||
@@ -160,13 +160,15 @@ class ShowTemplate {
|
|||||||
if ( $fudge === $this->template || $fudge === false ) {
|
if ( $fudge === $this->template || $fudge === false ) {
|
||||||
echo wp_kses_post( "<!-- Active Template: {$this->template} -->\n" );
|
echo wp_kses_post( "<!-- Active Template: {$this->template} -->\n" );
|
||||||
} else {
|
} else {
|
||||||
echo esc_html( "<!--\n" );
|
// phpcs:disable
|
||||||
echo esc_html( "The template loader logic has chosen a different template than what was used.\n\n" );
|
echo "<!--\n";
|
||||||
echo esc_html( "Chosen Template: {$this->template}\n" );
|
echo "The template loader logic has chosen a different template than what was used.\n\n";
|
||||||
echo esc_html( "Actual Template: $fudge\n\n" );
|
echo "Chosen Template: {$this->template}\n";
|
||||||
echo esc_html( "This will usually occur if the template file was overriden using an action on template_redirect.\n" );
|
echo "Actual Template: $fudge\n\n";
|
||||||
echo esc_html( "This is a best effort guess to catch such scenarios as mentioned above but can be incorrect.\n" );
|
echo "This will usually occur if the template file was overriden using an action on template_redirect.\n";
|
||||||
echo esc_html( "-->\n" );
|
echo "This is a best effort guess to catch such scenarios as mentioned above but can be incorrect.\n";
|
||||||
|
echo "-->\n";
|
||||||
|
// phpcs:enable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-8
@@ -17,7 +17,11 @@ get_header();
|
|||||||
while ( have_posts() ) :
|
while ( have_posts() ) :
|
||||||
the_post();
|
the_post();
|
||||||
$project_id = (int) get_the_ID();
|
$project_id = (int) get_the_ID();
|
||||||
$provider = Projects::get_provider_label( $project_id );
|
$is_website = Projects::is_website( $project_id );
|
||||||
|
$site_url = Projects::get_site_url( $project_id );
|
||||||
|
$site_platform = Projects::get_site_platform( $project_id );
|
||||||
|
$site_shot_url = Projects::get_site_screenshot_url( $project_id, 'large' );
|
||||||
|
$provider = $is_website ? __( 'Website', 'ks-portfolio' ) : Projects::get_provider_label( $project_id );
|
||||||
$version = Projects::get_latest_version( $project_id );
|
$version = Projects::get_latest_version( $project_id );
|
||||||
$release = Projects::get_release_url( $project_id );
|
$release = Projects::get_release_url( $project_id );
|
||||||
$repo = Projects::get_repo_browse_url( $project_id );
|
$repo = Projects::get_repo_browse_url( $project_id );
|
||||||
@@ -27,9 +31,7 @@ while ( have_posts() ) :
|
|||||||
$type_label = ! empty( $type_terms ) && ! is_wp_error( $type_terms ) ? strtoupper( $type_terms[0]->name ) : '';
|
$type_label = ! empty( $type_terms ) && ! is_wp_error( $type_terms ) ? strtoupper( $type_terms[0]->name ) : '';
|
||||||
$excerpt = has_excerpt( $project_id ) ? get_the_excerpt( $project_id ) : '';
|
$excerpt = has_excerpt( $project_id ) ? get_the_excerpt( $project_id ) : '';
|
||||||
?>
|
?>
|
||||||
<article class="single-project aura-bg">
|
<article class="single-project">
|
||||||
<?php get_template_part( 'views/partials/aura-bg' ); ?>
|
|
||||||
<div class="aura-bg__content">
|
|
||||||
<div class="container mx-auto my-section">
|
<div class="container mx-auto my-section">
|
||||||
|
|
||||||
<header class="single-project__hero">
|
<header class="single-project__hero">
|
||||||
@@ -41,7 +43,7 @@ while ( have_posts() ) :
|
|||||||
</p>
|
</p>
|
||||||
<h1 class="single-project__title">
|
<h1 class="single-project__title">
|
||||||
<?php echo esc_html( get_the_title( $project_id ) ); ?>
|
<?php echo esc_html( get_the_title( $project_id ) ); ?>
|
||||||
<?php if ( 'Unknown' !== $version ) : ?>
|
<?php if ( ! $is_website && 'Unknown' !== $version ) : ?>
|
||||||
<span class="single-project__version">v<?php echo esc_html( $version ); ?></span>
|
<span class="single-project__version">v<?php echo esc_html( $version ); ?></span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</h1>
|
</h1>
|
||||||
@@ -50,6 +52,13 @@ while ( have_posts() ) :
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="single-project__actions">
|
<div class="single-project__actions">
|
||||||
|
<?php if ( $is_website ) : ?>
|
||||||
|
<?php if ( $site_url ) : ?>
|
||||||
|
<a class="button button--primary" href="<?php echo esc_url( $site_url ); ?>" target="_blank" rel="noopener noreferrer">
|
||||||
|
<?php echo esc_html__( 'Visit Site', 'ks-portfolio' ); ?>
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php else : ?>
|
||||||
<?php if ( $release ) : ?>
|
<?php if ( $release ) : ?>
|
||||||
<a class="button button--primary" href="<?php echo esc_url( Projects::get_download_url( $project_id ) ); ?>" rel="nofollow">
|
<a class="button button--primary" href="<?php echo esc_url( Projects::get_download_url( $project_id ) ); ?>" rel="nofollow">
|
||||||
<?php echo esc_html__( 'Download', 'ks-portfolio' ); ?>
|
<?php echo esc_html__( 'Download', 'ks-portfolio' ); ?>
|
||||||
@@ -63,12 +72,19 @@ while ( have_posts() ) :
|
|||||||
<?php echo esc_html__( 'View Repo', 'ks-portfolio' ); ?>
|
<?php echo esc_html__( 'View Repo', 'ks-portfolio' ); ?>
|
||||||
</a>
|
</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="single-project__layout">
|
<div class="single-project__layout">
|
||||||
<div class="single-project__body">
|
<div class="single-project__body">
|
||||||
<?php if ( has_post_thumbnail( $project_id ) ) : ?>
|
<?php if ( $is_website && $site_shot_url ) : ?>
|
||||||
|
<figure class="single-project__screenshot">
|
||||||
|
<a href="<?php echo esc_url( $site_url ); ?>" target="_blank" rel="noopener noreferrer">
|
||||||
|
<img src="<?php echo esc_url( $site_shot_url ); ?>" alt="<?php echo esc_attr( get_the_title( $project_id ) ); ?>" />
|
||||||
|
</a>
|
||||||
|
</figure>
|
||||||
|
<?php elseif ( has_post_thumbnail( $project_id ) ) : ?>
|
||||||
<figure class="single-project__featured">
|
<figure class="single-project__featured">
|
||||||
<?php echo get_the_post_thumbnail( $project_id, 'large' ); ?>
|
<?php echo get_the_post_thumbnail( $project_id, 'large' ); ?>
|
||||||
</figure>
|
</figure>
|
||||||
@@ -80,7 +96,19 @@ while ( have_posts() ) :
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside class="single-project__sidebar" aria-label="<?php echo esc_attr__( 'Repository details', 'ks-portfolio' ); ?>">
|
<aside class="single-project__sidebar" aria-label="<?php echo esc_attr__( $is_website ? __( 'Website details', 'ks-portfolio' ) : __( 'Repository details', 'ks-portfolio' ) ); ?>">
|
||||||
|
<?php if ( $is_website ) : ?>
|
||||||
|
<?php if ( $site_platform ) : ?>
|
||||||
|
<section class="single-project__details">
|
||||||
|
<h3><?php echo esc_html__( 'Website Details', 'ks-portfolio' ); ?></h3>
|
||||||
|
<?php
|
||||||
|
set_query_var( 'label', __( 'Platform', 'ks-portfolio' ) );
|
||||||
|
set_query_var( 'value', $site_platform );
|
||||||
|
get_template_part( 'views/components/project-meta-row' );
|
||||||
|
?>
|
||||||
|
</section>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php else : ?>
|
||||||
<?php if ( $owner ) : ?>
|
<?php if ( $owner ) : ?>
|
||||||
<section class="single-project__maintainer">
|
<section class="single-project__maintainer">
|
||||||
<h3><?php echo esc_html__( 'Maintainer', 'ks-portfolio' ); ?></h3>
|
<h3><?php echo esc_html__( 'Maintainer', 'ks-portfolio' ); ?></h3>
|
||||||
@@ -157,10 +185,10 @@ while ( have_posts() ) :
|
|||||||
endforeach;
|
endforeach;
|
||||||
?>
|
?>
|
||||||
</section>
|
</section>
|
||||||
|
<?php endif; ?>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</article>
|
</article>
|
||||||
<?php
|
<?php
|
||||||
endwhile;
|
endwhile;
|
||||||
|
|||||||
Vendored
-6395
File diff suppressed because it is too large
Load Diff
@@ -54,6 +54,12 @@ class Navigation {
|
|||||||
*/
|
*/
|
||||||
#slidingViewportEnabled = false;
|
#slidingViewportEnabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function reference for preventing scroll (needed to remove listener)
|
||||||
|
* @type {Function}
|
||||||
|
*/
|
||||||
|
#preventScrollHandler = (e) => e.preventDefault();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
constructor(mobileMenuButtonId, dropDownClass, subMenuLinkClass = "sub-menu-item") {
|
constructor(mobileMenuButtonId, dropDownClass, subMenuLinkClass = "sub-menu-item") {
|
||||||
@@ -118,7 +124,14 @@ class Navigation {
|
|||||||
|
|
||||||
const isExpanded = button.getAttribute("aria-expanded") === "true";
|
const isExpanded = button.getAttribute("aria-expanded") === "true";
|
||||||
|
|
||||||
if (!isExpanded) {
|
if (isExpanded) {
|
||||||
|
// Prevent scroll when menu is open
|
||||||
|
document.documentElement.style.overflow = "hidden";
|
||||||
|
document.body.style.overflow = "hidden";
|
||||||
|
} else {
|
||||||
|
// Re-enable scroll when menu is closed
|
||||||
|
document.documentElement.style.overflow = "";
|
||||||
|
document.body.style.overflow = "";
|
||||||
// Reset sliding navigation when menu is closed
|
// Reset sliding navigation when menu is closed
|
||||||
if (this.#slidingViewportEnabled) {
|
if (this.#slidingViewportEnabled) {
|
||||||
this.resetSlidingNavigation();
|
this.resetSlidingNavigation();
|
||||||
@@ -172,12 +185,23 @@ class Navigation {
|
|||||||
&& !event.relatedTarget.classList.contains("menu-vdi__link")
|
&& !event.relatedTarget.classList.contains("menu-vdi__link")
|
||||||
&& !event.relatedTarget.classList.contains("menu-vdi__back"); // Don't close on back button click
|
&& !event.relatedTarget.classList.contains("menu-vdi__back"); // Don't close on back button click
|
||||||
|
|
||||||
if (isNull || isNotMenuItem)
|
if (isNull || isNotMenuItem) {
|
||||||
button.setAttribute("aria-expanded", false);
|
button.setAttribute("aria-expanded", false);
|
||||||
|
// Re-enable scroll when menu is closed
|
||||||
|
document.documentElement.style.overflow = "";
|
||||||
|
document.body.style.overflow = "";
|
||||||
|
}
|
||||||
}, true)
|
}, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles escape behaviour
|
||||||
|
* Closes all dropdown when user hits
|
||||||
|
* escape key
|
||||||
|
*
|
||||||
|
* @link {https://developer.mozilla.org/en-US/docs/Web/API/Element/keyup_event}
|
||||||
|
*/
|
||||||
/**
|
/**
|
||||||
* Handles escape behaviour
|
* Handles escape behaviour
|
||||||
* Closes all dropdown when user hits
|
* Closes all dropdown when user hits
|
||||||
@@ -191,6 +215,10 @@ class Navigation {
|
|||||||
this.#mobileMenuButton.setAttribute("aria-expanded", false);
|
this.#mobileMenuButton.setAttribute("aria-expanded", false);
|
||||||
this.closeAllDropDowns();
|
this.closeAllDropDowns();
|
||||||
|
|
||||||
|
// Re-enable scroll when menu is closed
|
||||||
|
document.documentElement.style.overflow = "";
|
||||||
|
document.body.style.overflow = "";
|
||||||
|
|
||||||
// Reset sliding navigation on escape
|
// Reset sliding navigation on escape
|
||||||
if (this.#slidingViewportEnabled) {
|
if (this.#slidingViewportEnabled) {
|
||||||
this.resetSlidingNavigation();
|
this.resetSlidingNavigation();
|
||||||
@@ -571,6 +599,44 @@ class Navigation {
|
|||||||
|
|
||||||
console.log('Sliding structure cleaned up');
|
console.log('Sliding structure cleaned up');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable scroll on the page
|
||||||
|
* Prevents scrolling in all directions
|
||||||
|
*/
|
||||||
|
disableScroll() {
|
||||||
|
try {
|
||||||
|
// Apply overflow hidden to both html and body for better compatibility
|
||||||
|
document.documentElement.style.overflow = "hidden";
|
||||||
|
document.body.style.overflow = "hidden";
|
||||||
|
|
||||||
|
// Prevent touch scroll on mobile devices
|
||||||
|
if (this.#preventScrollHandler) {
|
||||||
|
document.addEventListener("touchmove", this.#preventScrollHandler, { passive: false });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("Error disabling scroll:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable scroll on the page
|
||||||
|
* Restores normal scrolling behavior
|
||||||
|
*/
|
||||||
|
enableScroll() {
|
||||||
|
try {
|
||||||
|
// Restore overflow behavior
|
||||||
|
document.documentElement.style.overflow = "";
|
||||||
|
document.body.style.overflow = "";
|
||||||
|
|
||||||
|
// Re-enable touch scroll on mobile devices
|
||||||
|
if (this.#preventScrollHandler) {
|
||||||
|
document.removeEventListener("touchmove", this.#preventScrollHandler);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("Error enabling scroll:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Navigation;
|
export default Navigation;
|
||||||
|
|||||||
+26
-26
@@ -1,6 +1,6 @@
|
|||||||
/* Theme color definitions */
|
/* Theme color definitions */
|
||||||
|
|
||||||
@theme {
|
@theme static {
|
||||||
--color-black: oklch(0% 0 0);
|
--color-black: oklch(0% 0 0);
|
||||||
--color-white: oklch(100% 0 0);
|
--color-white: oklch(100% 0 0);
|
||||||
|
|
||||||
@@ -38,32 +38,32 @@
|
|||||||
--color-dark: oklch(34.51% 0.0133 248.2);
|
--color-dark: oklch(34.51% 0.0133 248.2);
|
||||||
|
|
||||||
/* Carbon Blue / dark surface tokens (Portfolio 2026) */
|
/* Carbon Blue / dark surface tokens (Portfolio 2026) */
|
||||||
--color-surface: #121318;
|
--color-surface: oklch(18.79% 0.0103 276.4);
|
||||||
--color-surface-dim: #121318;
|
--color-surface-dim: oklch(18.79% 0.0103 276.4);
|
||||||
--color-surface-bright: #38393f;
|
--color-surface-bright: oklch(34.59% 0.0105 278.3);
|
||||||
--color-surface-container-lowest: #0d0e13;
|
--color-surface-container-lowest: oklch(16.51% 0.0106 276.3);
|
||||||
--color-surface-container-low: #1a1b21;
|
--color-surface-container-low: oklch(22.37% 0.0117 277.9);
|
||||||
--color-surface-container: #1e1f25;
|
--color-surface-container: oklch(24.08% 0.0115 278);
|
||||||
--color-surface-container-high: #292a2f;
|
--color-surface-container-high: oklch(28.6% 0.0092 276.8);
|
||||||
--color-surface-container-highest:#34343a;
|
--color-surface-container-highest: oklch(32.74% 0.0105 285.8);
|
||||||
--color-on-surface: #e3e1e9;
|
--color-on-surface: oklch(91.36% 0.011 297.6);
|
||||||
--color-on-surface-variant:#c5c5d3;
|
--color-on-surface-variant: oklch(82.77% 0.0194 286);
|
||||||
--color-outline: #8f909d;
|
--color-outline: oklch(65.68% 0.0192 282.3);
|
||||||
--color-outline-variant: #444651;
|
--color-outline-variant: oklch(39.65% 0.0187 277.4);
|
||||||
--color-page-base: #100e0b;
|
--color-page-base: oklch(16.5% 0.007 78.1);
|
||||||
--color-backlight: rgba(96, 165, 250, 0.20);
|
--color-backlight: oklch(71.37% 0.1434 254.6 / 0.2);
|
||||||
|
|
||||||
--color-primary-fixed: #dce1ff;
|
--color-primary-fixed: oklch(91.52% 0.0412 278.1);
|
||||||
--color-primary-fixed-dim: #b6c4ff;
|
--color-primary-fixed-dim: oklch(83.03% 0.0845 274.1);
|
||||||
--color-on-primary: #05297a;
|
--color-on-primary: oklch(32.02% 0.1432 262.8);
|
||||||
--color-on-primary-fixed: #00164e;
|
--color-on-primary-fixed: oklch(23.06% 0.1072 262.1);
|
||||||
--color-on-primary-fixed-variant: #264191;
|
--color-on-primary-fixed-variant: oklch(40.35% 0.1363 266.3);
|
||||||
|
|
||||||
--color-primary-12: rgba(182, 196, 255, 0.12);
|
--color-primary-12: oklch(83.03% 0.0845 274.1 / 0.12);
|
||||||
--color-primary-87: #dce1ff;
|
--color-primary-87: oklch(91.52% 0.0412 278.1);
|
||||||
|
|
||||||
--color-success: #5ccf91;
|
--color-success: oklch(77.17% 0.1386 157.2);
|
||||||
--color-warning: #f4e04d;
|
--color-warning: oklch(91.37% 0.1234 97.2);
|
||||||
--color-danger: #cc3b3b;
|
--color-danger: oklch(54.12% 0.1456 29.8);
|
||||||
--color-info: #3d7ab0;
|
--color-info: oklch(45.67% 0.1345 210.3);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,12 +84,17 @@ main#maincontent {
|
|||||||
.embed { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; }
|
.embed { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; }
|
||||||
.embed iframe, .embed object, .embed embed, .embed video { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
|
.embed iframe, .embed object, .embed embed, .embed video { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
|
||||||
|
|
||||||
/* Carbon Blue atmospheric field (decorative — content must sit in z-index: 1) */
|
/* Carbon Blue atmospheric field — rendered once globally (in header.php)
|
||||||
|
* as a fixed-position layer behind all content. Decorative only; content
|
||||||
|
* stays clickable thanks to pointer-events: none. The body carries
|
||||||
|
* `background-color: #100e0b` so the gradient's mix-blend-mode layers
|
||||||
|
* have something to composite against. */
|
||||||
.aura-bg {
|
.aura-bg {
|
||||||
position: relative;
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-height: 100vh;
|
|
||||||
isolation: isolate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.aura-layer-1 {
|
.aura-layer-1 {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
* text-75px | 32.00 | 39.46 | 43.25 | 50.30 | 57.36 | 61.77 | 75.00
|
* text-75px | 32.00 | 39.46 | 43.25 | 50.30 | 57.36 | 61.77 | 75.00
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@theme {
|
@theme static {
|
||||||
--font-sans: "Raleway", sans-serif;
|
--font-sans: "Raleway", sans-serif;
|
||||||
--font-headings: var(--font-sans);
|
--font-headings: var(--font-sans);
|
||||||
--line-height: 1.6;
|
--line-height: 1.6;
|
||||||
@@ -140,10 +140,15 @@ p {
|
|||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul, ol { margin: 0 0 1rem 1.5rem; padding: 0; }
|
||||||
|
|
||||||
ul { list-style-type: disc; }
|
ul {
|
||||||
|
list-style-type: disc;
|
||||||
|
}
|
||||||
|
|
||||||
ol { list-style-type: decimal; }
|
ol {
|
||||||
|
list-style-type: decimal;
|
||||||
|
}
|
||||||
|
|
||||||
li ul, li ol { margin: 0 1rem; }
|
li ul, li ol { margin: 0 1rem; }
|
||||||
|
|
||||||
@@ -169,9 +174,7 @@ pre code {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code { @apply bg-white/30 px-0.25 py-0.25 font-mono text-black rounded-sm; }
|
||||||
@apply bg-black/30 px-[3px] py-0.5 font-mono text-black text-xs rounded-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
|
|||||||
+51
-43
@@ -1,6 +1,6 @@
|
|||||||
/* Button styles */
|
/* Button styles */
|
||||||
|
|
||||||
@theme {
|
@theme inline {
|
||||||
/* Configuration */
|
/* Configuration */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,14 +18,14 @@
|
|||||||
* --button-outline-color (fallback is --button-border-color)
|
* --button-outline-color (fallback is --button-border-color)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
--button-bg: var(--color-primary);
|
--button-bg: var(--color-on-primary-fixed-variant);
|
||||||
--button-color: var(--color-white);
|
--button-color: var(--color-primary-fixed-dim);
|
||||||
--button-hover-bg: var(--color-info);
|
--button-hover-bg: var(--color-info);
|
||||||
--button-hover-border-color: var(--color-info);
|
--button-hover-border-color: var(--color-info);
|
||||||
--button-hover-color: var(--color-white);
|
--button-hover-color: var(--color-white);
|
||||||
--button-border-width: 3px;
|
--button-border-width: 3px;
|
||||||
--button-border-style: solid;
|
--button-border-style: solid;
|
||||||
--button-border-color: var(--button-bg);
|
--button-border-color: var(--color-primary-fixed-dim);
|
||||||
--button-radius: 0.5rem;
|
--button-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,11 +50,14 @@
|
|||||||
|
|
||||||
&[data-button-variant="outline"] {
|
&[data-button-variant="outline"] {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
&:hover { background: transparent; }
|
color: var(--button-border-color);
|
||||||
|
border-color: var(--button-bg);
|
||||||
|
|
||||||
--button-color: var(--button-outline-color, var(--button-bg));
|
&:hover {
|
||||||
--button-hover-border-color: var(--button-hover-bg);
|
background: transparent;
|
||||||
--button-hover-color: var(--button-hover-bg);
|
border-color: var(--button-hover-bg);
|
||||||
|
color: var(--button-hover-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,20 +79,39 @@
|
|||||||
|
|
||||||
/* Back To Top Button */
|
/* Back To Top Button */
|
||||||
#backToTopBtn {
|
#backToTopBtn {
|
||||||
display:none;
|
|
||||||
position:fixed;
|
|
||||||
bottom:2rem;
|
|
||||||
right:2rem;
|
|
||||||
z-index:1000;
|
|
||||||
padding:0.75em 1.5em;
|
|
||||||
font-size:1.1rem;
|
|
||||||
border-radius:2em;
|
|
||||||
background: var(--color-primary,#3857BC);
|
background: var(--color-primary,#3857BC);
|
||||||
color:#fff;
|
|
||||||
border: none;
|
border: none;
|
||||||
|
border-radius: 2em;
|
||||||
|
bottom: 2rem;
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||||||
|
color: #fff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
display: none;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
padding: 0.75em 1.5em;
|
||||||
|
position: fixed;
|
||||||
|
right: 2rem;
|
||||||
transition: opacity 0.2s;
|
transition: opacity 0.2s;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top {
|
||||||
|
background: var(--color-primary, #3857BC);
|
||||||
|
border: none;
|
||||||
|
border-radius: 2em;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
opacity: 0.85;
|
||||||
|
padding: 0.75em 1.5em;
|
||||||
|
transition: opacity 0.2s, background 0.2s;
|
||||||
|
|
||||||
|
&:hover, &:focus {
|
||||||
|
background: var(--color-info, #233a7a);
|
||||||
|
opacity: 1;
|
||||||
|
outline: 2px solid var(--color-info, #233a7a);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -157,24 +179,6 @@ x-button:has(.button[data-button-width="full"]) { @apply w-full; }
|
|||||||
--button-hover-color: var(--color-dark);
|
--button-hover-color: var(--color-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-to-top {
|
|
||||||
background: var(--color-primary, #3857BC);
|
|
||||||
color: #fff;
|
|
||||||
border: none;
|
|
||||||
border-radius: 2em;
|
|
||||||
padding: 0.75em 1.5em;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: opacity 0.2s, background 0.2s;
|
|
||||||
opacity: 0.85;
|
|
||||||
}
|
|
||||||
.back-to-top:hover, .back-to-top:focus {
|
|
||||||
background: var(--color-info, #233a7a);
|
|
||||||
opacity: 1;
|
|
||||||
outline: 2px solid var(--color-info, #233a7a);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark-surface button variants (WCAG-AA against #100e0b) */
|
/* Dark-surface button variants (WCAG-AA against #100e0b) */
|
||||||
.button {
|
.button {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@@ -191,6 +195,7 @@ x-button:has(.button[data-button-width="full"]) { @apply w-full; }
|
|||||||
}
|
}
|
||||||
|
|
||||||
.button:active { transform: scale(0.99); }
|
.button:active { transform: scale(0.99); }
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) { .button:active { transform: none; } }
|
@media (prefers-reduced-motion: reduce) { .button:active { transform: none; } }
|
||||||
|
|
||||||
.button:focus-visible { outline: 2px solid var(--color-primary, #b6c4ff); outline-offset: 3px; }
|
.button:focus-visible { outline: 2px solid var(--color-primary, #b6c4ff); outline-offset: 3px; }
|
||||||
@@ -199,20 +204,22 @@ x-button:has(.button[data-button-width="full"]) { @apply w-full; }
|
|||||||
background: var(--color-primary, #b6c4ff);
|
background: var(--color-primary, #b6c4ff);
|
||||||
color: var(--color-on-primary, #05297a);
|
color: var(--color-on-primary, #05297a);
|
||||||
border-color: var(--color-primary, #b6c4ff);
|
border-color: var(--color-primary, #b6c4ff);
|
||||||
|
|
||||||
|
&:hover { background: var(--color-primary-87, #dce1ff); border-color: var(--color-primary-87, #dce1ff); }
|
||||||
}
|
}
|
||||||
.button--primary:hover { background: var(--color-primary-87, #dce1ff); border-color: var(--color-primary-87, #dce1ff); }
|
|
||||||
|
|
||||||
.button--outline {
|
.button--outline {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--color-on-surface, #e3e1e9);
|
|
||||||
border-color: var(--color-outline, #8f909d);
|
border-color: var(--color-outline, #8f909d);
|
||||||
|
color: var(--color-on-surface, #e3e1e9);
|
||||||
|
|
||||||
|
&:hover { border-color: var(--color-primary, #b6c4ff); color: var(--color-primary, #b6c4ff); }
|
||||||
}
|
}
|
||||||
.button--outline:hover { border-color: var(--color-primary, #b6c4ff); color: var(--color-primary, #b6c4ff); }
|
|
||||||
|
|
||||||
.button--secondary {
|
.button--secondary {
|
||||||
background: var(--color-surface-container-high, #292a2f);
|
background: var(--color-surface-container-high, #292a2f);
|
||||||
color: var(--color-on-surface, #e3e1e9);
|
|
||||||
border-color: var(--color-outline-variant, #444651);
|
border-color: var(--color-outline-variant, #444651);
|
||||||
|
color: var(--color-on-surface, #e3e1e9);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Outline button over the site-header's bg-secondary surface needs lighter text
|
/* Outline button over the site-header's bg-secondary surface needs lighter text
|
||||||
@@ -220,10 +227,11 @@ x-button:has(.button[data-button-width="full"]) { @apply w-full; }
|
|||||||
* Pure white clears 4.5:1 against #6e757f; --color-on-primary (#05297a) is too
|
* Pure white clears 4.5:1 against #6e757f; --color-on-primary (#05297a) is too
|
||||||
* dark and drops to 2.81:1, so we go to --color-white instead. */
|
* dark and drops to 2.81:1, so we go to --color-white instead. */
|
||||||
.site-header .button--outline {
|
.site-header .button--outline {
|
||||||
color: var(--color-white, #ffffff);
|
|
||||||
border-color: var(--color-white, #ffffff);
|
border-color: var(--color-white, #ffffff);
|
||||||
}
|
color: var(--color-white, #ffffff);
|
||||||
.site-header .button--outline:hover {
|
|
||||||
color: var(--color-primary-87, #dce1ff);
|
&:hover {
|
||||||
border-color: var(--color-primary-87, #dce1ff);
|
border-color: var(--color-primary-87, #dce1ff);
|
||||||
|
color : var(--color-primary-87, #dce1ff);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 4;
|
-webkit-line-clamp: 4;
|
||||||
|
line-clamp: 4;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@@ -96,23 +97,22 @@
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
transition: color 200ms ease, background-color 200ms ease, border-color 200ms ease;
|
transition: color 200ms ease, background-color 200ms ease, border-color 200ms ease;
|
||||||
|
|
||||||
|
&:hover { background: var(--color-primary-87); border-color: var(--color-primary-87); }
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-card__action--primary {
|
.project-card__action--primary {
|
||||||
background: var(--color-primary, #b6c4ff);
|
background: var(--color-primary);
|
||||||
color: var(--color-on-primary, #05297a);
|
color: #fff;
|
||||||
border-color: var(--color-primary, #b6c4ff);
|
border-color: var(--color-primary, #b6c4ff);
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-card__action--primary:hover { background: var(--color-primary-87); border-color: var(--color-primary-87); }
|
|
||||||
|
|
||||||
.project-card__action--secondary {
|
.project-card__action--secondary {
|
||||||
color: var(--color-on-surface, #e3e1e9);
|
color: var(--color-on-surface, #e3e1e9);
|
||||||
border-color: var(--color-outline, #8f909d);
|
border-color: var(--color-outline, #8f909d);
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-card__action--secondary:hover { border-color: var(--color-primary, #b6c4ff); color: var(--color-primary, #b6c4ff); }
|
|
||||||
|
|
||||||
.project-card__action:focus-visible {
|
.project-card__action:focus-visible {
|
||||||
outline: 2px solid var(--color-primary, #b6c4ff);
|
outline: 2px solid var(--color-primary, #b6c4ff);
|
||||||
outline-offset: 3px;
|
outline-offset: 3px;
|
||||||
@@ -207,12 +207,34 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.archive-projects__back-link {
|
/* Project-type taxonomy archive — the page hero renders the heading,
|
||||||
color: var(--color-primary, #b6c4ff);
|
* breadcrumbs handle navigation. */
|
||||||
text-decoration: none;
|
.tax-project { padding-top: 2rem; padding-bottom: 4rem; }
|
||||||
font-weight: 600;
|
|
||||||
|
.tax-project__pagination {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tax-project__empty {
|
||||||
|
text-align: center;
|
||||||
|
padding: 4rem 1rem;
|
||||||
|
color: var(--color-on-surface-variant, #c5c5d3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tax-project__empty-title {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
color: var(--color-on-surface, #e3e1e9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tax-project__pagination a,
|
||||||
|
.tax-project__pagination span {
|
||||||
|
background: var(--color-surface-container, #1e1f25);
|
||||||
|
color: var(--color-on-surface, #e3e1e9);
|
||||||
|
border: 1px solid var(--color-outline-variant, #444651);
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
.archive-projects__back-link:hover { text-decoration: underline; }
|
|
||||||
|
|
||||||
.single-project__hero {
|
.single-project__hero {
|
||||||
background: var(--color-surface-container-low, #1a1b21);
|
background: var(--color-surface-container-low, #1a1b21);
|
||||||
@@ -300,25 +322,6 @@
|
|||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-hero { min-height: 60vh; display: flex; align-items: center; padding: 4rem 0; }
|
|
||||||
.home-hero__inner { max-width: 50rem; }
|
|
||||||
.home-hero__heading {
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: clamp(2rem, 4vw, 3.5rem);
|
|
||||||
line-height: 1.1;
|
|
||||||
color: var(--color-on-surface, #e3e1e9);
|
|
||||||
margin: 0 0 1rem;
|
|
||||||
}
|
|
||||||
.home-hero__intro {
|
|
||||||
color: var(--color-on-surface-variant, #c5c5d3);
|
|
||||||
font-size: clamp(1rem, 1.5vw, 1.25rem);
|
|
||||||
line-height: 1.6;
|
|
||||||
max-width: 50ch;
|
|
||||||
margin: 0 0 1.5rem;
|
|
||||||
}
|
|
||||||
.home-hero__actions { display: flex; flex-wrap: wrap; gap: 0.75rem; }
|
|
||||||
|
|
||||||
.home-recent__head {
|
.home-recent__head {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
@@ -346,3 +349,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.home-recent__empty { text-align: center; padding: 4rem 1rem; color: var(--color-on-surface-variant, #c5c5d3); }
|
.home-recent__empty { text-align: center; padding: 4rem 1rem; color: var(--color-on-surface-variant, #c5c5d3); }
|
||||||
|
|
||||||
|
/* Website-mode project card + single-project screenshot/platform */
|
||||||
|
.single-project__screenshot { margin: 0 0 1.5rem; }
|
||||||
|
.single-project__screenshot img { width: 100%; height: auto; border-radius: 0.75rem; display: block; border: 1px solid var(--color-outline-variant, #444651); }
|
||||||
|
.project-card__platform { color: var(--color-on-surface-variant, #c5c5d3); font-size: 0.875rem; }
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
background: var(--color-surface, #121318);
|
background: var(--color-surface, #121318);
|
||||||
color: var(--color-on-surface, #e3e1e9);
|
color: var(--color-on-surface, #e3e1e9);
|
||||||
border-top: 1px solid var(--color-outline-variant, #444651);
|
border-top: 1px solid var(--color-outline-variant, #444651);
|
||||||
}
|
|
||||||
.site-footer__title a { color: var(--color-on-surface, #e3e1e9); text-decoration: none; }
|
.site-footer__title a { color: #e3e1e9; text-decoration: none; }
|
||||||
.site-footer__desc { color: var(--color-on-surface-variant, #c5c5d3); }
|
|
||||||
.site-footer__area h3,
|
.site-footer__area h3,
|
||||||
.site-footer__nav h3 {
|
.site-footer__nav h3 {
|
||||||
color: var(--color-on-surface, #e3e1e9);
|
color: var(--color-on-surface, #e3e1e9);
|
||||||
@@ -16,8 +16,11 @@
|
|||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
border-bottom: 1px solid var(--color-outline-variant, #444651);
|
border-bottom: 1px solid var(--color-outline-variant, #444651);
|
||||||
}
|
}
|
||||||
.site-footer a { color: var(--color-primary, #b6c4ff); }
|
|
||||||
|
.site-footer a { color: #b6c4ff; }
|
||||||
|
|
||||||
.site-footer a:hover { text-decoration: underline; }
|
.site-footer a:hover { text-decoration: underline; }
|
||||||
|
|
||||||
.site-footer__copyright {
|
.site-footer__copyright {
|
||||||
background: var(--color-surface-container-lowest, #0d0e13);
|
background: var(--color-surface-container-lowest, #0d0e13);
|
||||||
color: var(--color-on-surface-variant, #c5c5d3);
|
color: var(--color-on-surface-variant, #c5c5d3);
|
||||||
@@ -25,3 +28,126 @@
|
|||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Social media links styling */
|
||||||
|
.social-links {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-links a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SVG icon alignment */
|
||||||
|
.site-footer svg {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Quick Links styling */
|
||||||
|
.site-footer__nav ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer__nav > ul > li {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer__nav > ul > li:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main menu items with SVG marker */
|
||||||
|
.site-footer__nav > ul > li > a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer__nav > ul > li > a::before {
|
||||||
|
content: '';
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg width='24' height='25' viewBox='0 0 24 25' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M7.15533 5.59469C6.94083 5.38019 6.61824 5.31603 6.33799 5.43211C6.05773 5.5482 5.875 5.82168 5.875 6.12502V18.625C5.875 18.9284 6.05773 19.2018 6.33799 19.3179C6.61824 19.434 6.94083 19.3699 7.15533 19.1554L13.4053 12.9054C13.6982 12.6125 13.6982 12.1376 13.4053 11.8447L7.15533 5.59469Z' fill='%23b6c4ff'/%3E%3Cpath opacity='0.4' d='M11.6553 5.59467C11.3624 5.30178 10.8876 5.30178 10.5947 5.59467C10.3018 5.88756 10.3018 6.36244 10.5947 6.65533L16.3143 12.375L10.5947 18.0947C10.3018 18.3876 10.3018 18.8624 10.5947 19.1553C10.8876 19.4482 11.3624 19.4482 11.6553 19.1553L17.9053 12.9053C18.1982 12.6124 18.1982 12.1376 17.9053 11.8447L11.6553 5.59467Z' fill='%23b6c4ff'/%3E%3C/svg%3E");
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Submenu styling */
|
||||||
|
.site-footer__nav-submenu {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0.5rem 0 0 4rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer__nav-submenu-item {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer__nav-submenu-item a {
|
||||||
|
color: #b6c4ff;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer__nav-submenu-item a::before {
|
||||||
|
content: '';
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg width='24' height='25' viewBox='0 0 24 25' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M7.15533 5.59469C6.94083 5.38019 6.61824 5.31603 6.33799 5.43211C6.05773 5.5482 5.875 5.82168 5.875 6.12502V18.625C5.875 18.9284 6.05773 19.2018 6.33799 19.3179C6.61824 19.434 6.94083 19.3699 7.15533 19.1554L13.4053 12.9054C13.6982 12.6125 13.6982 12.1376 13.4053 11.8447L7.15533 5.59469Z' fill='%23b6c4ff'/%3E%3Cpath opacity='0.4' d='M11.6553 5.59467C11.3624 5.30178 10.8876 5.30178 10.5947 5.59467C10.3018 5.88756 10.3018 6.36244 10.5947 6.65533L16.3143 12.375L10.5947 18.0947C10.3018 18.3876 10.3018 18.8624 10.5947 19.1553C10.8876 19.4482 11.3624 19.4482 11.6553 19.1553L17.9053 12.9053C18.1982 12.6124 18.1982 12.1376 17.9053 11.8447L11.6553 5.59467Z' fill='%23b6c4ff'/%3E%3C/svg%3E");
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer__nav-submenu a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Contact button styling */
|
||||||
|
.site-footer__nav-contact > a.button {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
background-color: #b6c4ff;
|
||||||
|
color: #121318;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer__nav-contact > a.button::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer__nav-contact > a.button:hover {
|
||||||
|
background-color: #e3e1e9;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer__nav-contact {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
border-top: 1px solid var(--color-outline-variant, #444651);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,49 +29,40 @@
|
|||||||
|
|
||||||
@media screen and (min-width: 62.5rem) {
|
@media screen and (min-width: 62.5rem) {
|
||||||
.menu-vdi {
|
.menu-vdi {
|
||||||
.menu-vdi__toggle {
|
.menu-vdi__toggle { @apply flex items-center gap-2; }
|
||||||
@apply flex items-center gap-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-vdi__item--parent {
|
.menu-vdi__item--parent { @apply relative; }
|
||||||
@apply relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-vdi__submenu {
|
.menu-vdi__submenu {
|
||||||
@apply bg-white shadow-lg left-4 w-64 flex-col;
|
@apply bg-white shadow-lg left-4 w-64 flex-col;
|
||||||
top: calc(100% + 1rem);
|
top: calc(100% + 1rem);
|
||||||
|
|
||||||
>li {
|
>li { @apply w-full; }
|
||||||
@apply w-full;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-vdi__item {
|
.menu-vdi__item {
|
||||||
/* text */
|
/* text */
|
||||||
@apply font-bold text-18px text-black hover:text-light no-underline leading-snug;
|
@apply font-bold text-18px text-black! hover:text-light! no-underline leading-snug;
|
||||||
/* spacing & display */
|
/* spacing & display */
|
||||||
@apply block w-full;
|
@apply block w-full;
|
||||||
/* interaction */
|
/* interaction */
|
||||||
@apply focus-visible:bg-secondary-200 hover:bg-secondary-200;
|
@apply focus-visible:bg-secondary-200 hover:bg-secondary-200;
|
||||||
|
|
||||||
a {
|
a { @apply block w-full; }
|
||||||
@apply block w-full;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a.menu-vdi__item,
|
a.menu-vdi__item,
|
||||||
.menu-vdi__item a {
|
.menu-vdi__item a { @apply p-4; }
|
||||||
@apply p-4;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-main a,
|
.nav-main a,
|
||||||
.nav-aux a {
|
.nav-aux a {
|
||||||
color: var(--color-on-surface, #e3e1e9);
|
color: #000;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-main a:hover,
|
.nav-main a:hover,
|
||||||
.nav-aux a:hover {
|
.nav-aux a:hover {
|
||||||
color: var(--color-primary, #b6c4ff);
|
color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Project Type Taxonomy Archive Template
|
|
||||||
*
|
|
||||||
* @package KsPortfolio
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace KsPortfolio;
|
|
||||||
|
|
||||||
get_header();
|
|
||||||
|
|
||||||
$queried = get_queried_object();
|
|
||||||
$term_name = $queried && ! is_wp_error( $queried ) ? $queried->name : __( 'Projects', 'ks-portfolio' );
|
|
||||||
?>
|
|
||||||
<section class="archive-projects aura-bg">
|
|
||||||
<?php get_template_part( 'views/partials/aura-bg' ); ?>
|
|
||||||
<div class="aura-bg__content">
|
|
||||||
<div class="container mx-auto my-section">
|
|
||||||
<header class="archive-projects__head">
|
|
||||||
<h1 class="archive-projects__title"><?php echo esc_html( $term_name ); ?></h1>
|
|
||||||
<p class="archive-projects__intro">
|
|
||||||
<a class="archive-projects__back-link" href="<?php echo esc_url( get_post_type_archive_link( 'projects' ) ); ?>">
|
|
||||||
<?php echo esc_html__( '← All Projects', 'ks-portfolio' ); ?>
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<?php if ( have_posts() ) : ?>
|
|
||||||
<div class="archive-projects__grid grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
||||||
<?php
|
|
||||||
while ( have_posts() ) :
|
|
||||||
the_post();
|
|
||||||
get_template_part( 'views/components/project-card' );
|
|
||||||
endwhile;
|
|
||||||
?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="archive-projects__pagination">
|
|
||||||
<?php
|
|
||||||
the_posts_pagination(
|
|
||||||
array(
|
|
||||||
'mid_size' => 2,
|
|
||||||
'prev_text' => '« ' . esc_html__( 'Previous', 'ks-portfolio' ),
|
|
||||||
'next_text' => esc_html__( 'Next', 'ks-portfolio' ) . ' »',
|
|
||||||
)
|
|
||||||
);
|
|
||||||
?>
|
|
||||||
</div>
|
|
||||||
<?php else : ?>
|
|
||||||
<div class="archive-projects__empty">
|
|
||||||
<h2 class="archive-projects__empty-title"><?php echo esc_html__( 'Nothing here yet…', 'ks-portfolio' ); ?></h2>
|
|
||||||
<p class="archive-projects__empty-desc"><?php echo esc_html__( 'No published projects found in this category.', 'ks-portfolio' ); ?></p>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<?php
|
|
||||||
get_footer();
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
const { test, expect } = require('@playwright/test');
|
||||||
|
|
||||||
|
test.use({ viewport: { width: 1920, height: 1080 } });
|
||||||
|
|
||||||
|
test.describe('aura-bg Carbon Blue gradient (global, fixed background)', () => {
|
||||||
|
test('body carries the dark base color that the aura layers blend against', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
// Without the body color, mix-blend-mode on the aura layers has nothing
|
||||||
|
// to composite against and the gradient disappears.
|
||||||
|
const bodyBg = await page.evaluate(() => getComputedStyle(document.body).backgroundColor);
|
||||||
|
// #100e0b = rgb(16, 14, 11)
|
||||||
|
expect(bodyBg).toBe('rgb(16, 14, 11)');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('.aura-bg renders globally as a fixed viewport layer (not a per-template wrapper)', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
// aura-bg is rendered once in header.php and must be position:fixed so
|
||||||
|
// it covers the entire viewport behind all content regardless of scroll.
|
||||||
|
const auraBg = page.locator('.aura-bg').first();
|
||||||
|
await expect(auraBg).toBeAttached();
|
||||||
|
|
||||||
|
const position = await auraBg.evaluate((el) => getComputedStyle(el).position);
|
||||||
|
expect(position).toBe('fixed');
|
||||||
|
|
||||||
|
// z-index: -1 puts the gradient behind page content. Content must sit
|
||||||
|
// on top via its own stacking context (which the body and main do not
|
||||||
|
// create, so the negative z-index is safe).
|
||||||
|
const zIndex = await auraBg.evaluate((el) => getComputedStyle(el).zIndex);
|
||||||
|
expect(zIndex).toBe('-1');
|
||||||
|
|
||||||
|
// pointer-events: none so clicks pass through to content underneath.
|
||||||
|
const pointerEvents = await auraBg.evaluate((el) => getComputedStyle(el).pointerEvents);
|
||||||
|
expect(pointerEvents).toBe('none');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('.aura-layer-1 renders the Carbon Blue linear gradient', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
const layer1 = page.locator('.aura-layer-1').first();
|
||||||
|
await expect(layer1).toBeAttached();
|
||||||
|
|
||||||
|
const bgImage = await layer1.evaluate((el) => getComputedStyle(el).backgroundImage);
|
||||||
|
expect(bgImage).toMatch(/linear-gradient/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('.aura-bg is not isolated (no stacking context traps blend modes)', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
// isolation: isolate on the .aura-bg parent would trap mix-blend-mode
|
||||||
|
// children inside an empty container, preventing them from blending
|
||||||
|
// with the body's #100e0b beneath. The global fixed layer must let
|
||||||
|
// blend modes reach through to the body.
|
||||||
|
const isolation = await page.locator('.aura-bg').first().evaluate((el) => getComputedStyle(el).isolation);
|
||||||
|
expect(isolation).not.toBe('isolate');
|
||||||
|
});
|
||||||
|
});
|
||||||
+1
-1
@@ -9,7 +9,7 @@ test.describe('home', () => {
|
|||||||
await page.screenshot({ path: 'test-results/home.png', fullPage: true });
|
await page.screenshot({ path: 'test-results/home.png', fullPage: true });
|
||||||
|
|
||||||
// Hero region must be present.
|
// Hero region must be present.
|
||||||
await expect(page.locator('.home-hero')).toBeVisible();
|
await expect(page.locator('.homepage-hero')).toBeVisible();
|
||||||
|
|
||||||
// Either the grid OR the empty state must be present.
|
// Either the grid OR the empty state must be present.
|
||||||
const grid = page.locator('.home-recent__grid');
|
const grid = page.locator('.home-recent__grid');
|
||||||
|
|||||||
@@ -8,25 +8,26 @@
|
|||||||
namespace KsPortfolio;
|
namespace KsPortfolio;
|
||||||
|
|
||||||
// Retrieve ACF fields
|
// Retrieve ACF fields
|
||||||
|
$imgHero = get_field( 'hero_image' );
|
||||||
$heading = get_field( 'heading' );
|
$heading = get_field( 'heading' );
|
||||||
$intro = get_field( 'intro' );
|
$intro = get_field( 'intro' );
|
||||||
$ctas = get_field( 'calls_to_action' );
|
$ctas = get_field( 'calls_to_action' );
|
||||||
|
|
||||||
$classes = 'homepage-hero mx-break-out bg-black bg-cover bg-no-repeat text-light py-12 lg:py-16 overflow-hidden';
|
$classes = 'homepage-hero mx-break-out text-light py-12 lg:py-16 overflow-hidden';
|
||||||
$wrapper = blockWrapperAttributes( $classes, $is_preview );
|
$wrapper = blockWrapperAttributes( $classes, $is_preview );
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<section <?php echo wp_kses_post( $wrapper ); ?>>
|
<section <?php echo wp_kses_post( $wrapper ); ?>>
|
||||||
<div class="container content-wrapper">
|
<div class="container content-wrapper flex flex-col lg:flex-row items-start justify-between">
|
||||||
<div class="max-w-lg sm:text-center lg:text-left lg:items-center ml-0">
|
<div class="max-w-2xl sm:text-center lg:text-left lg:items-center ml-0">
|
||||||
<?php if ( ! empty( $heading ) ) : ?>
|
<?php if ( ! empty( $heading ) ) : ?>
|
||||||
<h1 class="text-4xl lg:text-5xl font-bold leading-tight mb-4">
|
<h1 class="text-45px font-bold leading-tight mb-4">
|
||||||
<?php echo esc_html( $heading ); ?>
|
<?php echo esc_html( $heading ); ?>
|
||||||
</h1>
|
</h1>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ( ! empty( $intro ) ) : ?>
|
<?php if ( ! empty( $intro ) ) : ?>
|
||||||
<div class="mt-3 text-base text-light sm:mt-5 sm:text-xl lg:text-lg xl:text-xl with:max-w-full">
|
<div class="mt-3 text-20p text-light sm:mt-5 sm:text-xl lg:text-lg xl:text-xl with:max-w-full">
|
||||||
<?php echo wp_kses_post( $intro ); ?>
|
<?php echo wp_kses_post( $intro ); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -62,5 +63,17 @@ $wrapper = blockWrapperAttributes( $classes, $is_preview );
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-1 max-w-2xl">
|
||||||
|
<?php if ( ! empty( $imgHero ) ) : ?>
|
||||||
|
<div class="">
|
||||||
|
<img
|
||||||
|
src="<?php echo esc_url( $imgHero['url'] ); ?>"
|
||||||
|
alt="<?php echo esc_attr( $imgHero['alt'] ); ?>"
|
||||||
|
class="w-full h-full object-cover rounded-lg shadow-lg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "acf/recent-projects",
|
||||||
|
"title": "Recent Projects",
|
||||||
|
"description": "Block to display recent projects on the home page.",
|
||||||
|
"style": [
|
||||||
|
"file:./recent-projects.css"
|
||||||
|
],
|
||||||
|
"category": "sf-blocks",
|
||||||
|
"icon": "block-default",
|
||||||
|
"keywords": [
|
||||||
|
"recent-projects"
|
||||||
|
],
|
||||||
|
"acf": {
|
||||||
|
"mode": "preview",
|
||||||
|
"renderTemplate": "recent-projects.php"
|
||||||
|
},
|
||||||
|
"supports": {
|
||||||
|
"align": true,
|
||||||
|
"anchor": true,
|
||||||
|
"color": true,
|
||||||
|
"html": false,
|
||||||
|
"jsx": false,
|
||||||
|
"mode": true,
|
||||||
|
"multiple": false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Block Name: Recent Projects
|
||||||
|
|
||||||
|
* Block to display recent projects on the home page.
|
||||||
|
*
|
||||||
|
* @package KsPortfolio
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace KsPortfolio;
|
||||||
|
|
||||||
|
$classes = 'home-recent container mx-auto my-section';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: DO NOT remove this function call - it is required to avoid editor issues.
|
||||||
|
* $is_preview is a WordPress global when in the editor.
|
||||||
|
*/
|
||||||
|
$wrapper = blockWrapperAttributes( $classes, $is_preview );
|
||||||
|
?>
|
||||||
|
|
||||||
|
<section <?php echo wp_kses_post( $wrapper ); ?>>
|
||||||
|
<header class="home-recent__head">
|
||||||
|
<h2 class="home-recent__title"><?php echo esc_html__( 'Recent Projects', 'ks-portfolio' ); ?></h2>
|
||||||
|
|
||||||
|
<a class="home-recent__view-all" href="<?php echo esc_url( get_post_type_archive_link( 'projects' ) ); ?>">
|
||||||
|
<?php echo esc_html__( 'View All', 'ks-portfolio' ); ?> →
|
||||||
|
</a>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<hr class="home-recent__divider" />
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$recent = new \WP_Query(
|
||||||
|
array(
|
||||||
|
'post_type' => 'projects',
|
||||||
|
'posts_per_page' => 3,
|
||||||
|
'no_found_rows' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( $recent->have_posts() ) :
|
||||||
|
?>
|
||||||
|
<div class="home-recent__grid grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
<?php
|
||||||
|
while ( $recent->have_posts() ) :
|
||||||
|
$recent->the_post();
|
||||||
|
|
||||||
|
get_template_part( 'views/components/project-card' );
|
||||||
|
endwhile;
|
||||||
|
|
||||||
|
wp_reset_postdata();
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
<?php else : ?>
|
||||||
|
<div class="home-recent__empty">
|
||||||
|
<h3><?php echo esc_html__( 'Nothing here yet…', 'ks-portfolio' ); ?></h3>
|
||||||
|
<p><?php echo esc_html__( 'No published projects found.', 'ks-portfolio' ); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</section>
|
||||||
@@ -16,6 +16,11 @@ if ( $location === 'main_navigation' ) {
|
|||||||
} else {
|
} else {
|
||||||
$menuID = 'menu-container-aux';
|
$menuID = 'menu-container-aux';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resume link is exposed by header.php via $GLOBALS['ks_portfolio_link_resume']
|
||||||
|
// and read here so it can be appended as a mobile-only CTA at the bottom of
|
||||||
|
// the menu panel (hidden on desktop via lg:hidden).
|
||||||
|
$linkResume = isset( $GLOBALS['ks_portfolio_link_resume'] ) ? $GLOBALS['ks_portfolio_link_resume'] : '';
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<ul id="<?php echo esc_attr( $menuID ); ?>" class="menu-vdi">
|
<ul id="<?php echo esc_attr( $menuID ); ?>" class="menu-vdi">
|
||||||
@@ -30,4 +35,12 @@ if ( $location === 'main_navigation' ) {
|
|||||||
<?php include __DIR__ . '/single.php'; ?>
|
<?php include __DIR__ . '/single.php'; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<?php if ( ! empty( $linkResume ) && $location === 'main_navigation' ) : ?>
|
||||||
|
<li class="menu-vdi__item menu-item--resume lg:hidden border-t border-secondary-200 mt-2 pt-2">
|
||||||
|
<a href="<?php echo esc_url( $linkResume ); ?>" class="menu-vdi__link button button--primary mx-4 inline-block">
|
||||||
|
<?php echo esc_html__( 'Resume', 'ks-portfolio' ); ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
namespace KsPortfolio;
|
namespace KsPortfolio;
|
||||||
|
|
||||||
global $views;
|
global $views;
|
||||||
|
|
||||||
|
// Expose resume link to the menu items renderer so the CTA can be appended
|
||||||
|
// as the final <li> in the mobile panel (hidden on desktop via lg:hidden).
|
||||||
|
$GLOBALS['ks_portfolio_link_resume'] = isset( $GLOBALS['ks_portfolio_link_resume'] ) ? $GLOBALS['ks_portfolio_link_resume'] : '';
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<nav class="nav-main" role="navigation" aria-label="Main">
|
<nav class="nav-main" role="navigation" aria-label="Main">
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ $navIcon = isset( get_field( 'header', 'option' )['nav_icon'] ) ? get_field( 'he
|
|||||||
|
|
||||||
// Generate toggle classes
|
// Generate toggle classes
|
||||||
$toggleClasses = array( 'nav-main__toggle' );
|
$toggleClasses = array( 'nav-main__toggle' );
|
||||||
|
|
||||||
if ( $navIcon ) {
|
if ( $navIcon ) {
|
||||||
$toggleClasses[] = 'custom-icon';
|
$toggleClasses[] = 'custom-icon';
|
||||||
}
|
}
|
||||||
@@ -29,17 +30,18 @@ if ( $navIcon ) {
|
|||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<!-- Hamburger Icon -->
|
<!-- Hamburger Icon -->
|
||||||
<span class="nav-toggle-icon nav-toggle-hamburger">
|
<span class="nav-toggle-icon nav-toggle-hamburger">
|
||||||
<svg class="text-black fill-black" viewBox="0 0 100 80" width="40" height="40">
|
<svg class="text-current fill-[#1A2A45]" viewBox="0 0 100 60" width="40" height="40">
|
||||||
<rect y="0" width="100" height="15"></rect>
|
<rect y="0" width="100" height="15"></rect>
|
||||||
<rect y="30" width="100" height="15"></rect>
|
<rect y="30" width="100" height="15"></rect>
|
||||||
<rect y="60" width="100" height="15"></rect>
|
<rect y="60" width="100" height="15"></rect>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- X Icon -->
|
<!-- X Icon -->
|
||||||
<span class="nav-toggle-icon nav-toggle-x">
|
<span class="nav-toggle-icon nav-toggle-x">
|
||||||
<svg class="text-black fill-black" viewBox="0 0 100 80" width="40" height="40">
|
<svg class="text-current stroke-[#1A2A45]" viewBox="0 0 100 100" width="40" height="40">
|
||||||
<line x1="20" y1="20" x2="80" y2="60" stroke="black" stroke-width="15" stroke-linecap="round" />
|
<line x1="15" y1="15" x2="85" y2="85" stroke-width="15" stroke-linecap="round" />
|
||||||
<line x1="80" y1="20" x2="20" y2="60" stroke="black" stroke-width="15" stroke-linecap="round" />
|
<line x1="85" y1="15" x2="15" y2="85" stroke-width="15" stroke-linecap="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|||||||
@@ -17,14 +17,18 @@ if ( ! isset( $post ) || ! ( $post instanceof \WP_Post ) ) {
|
|||||||
$project_id = (int) $post->ID;
|
$project_id = (int) $post->ID;
|
||||||
$type_label = '';
|
$type_label = '';
|
||||||
$type_terms = get_the_terms( $project_id, 'project-type' );
|
$type_terms = get_the_terms( $project_id, 'project-type' );
|
||||||
|
|
||||||
if ( ! empty( $type_terms ) && ! is_wp_error( $type_terms ) ) {
|
if ( ! empty( $type_terms ) && ! is_wp_error( $type_terms ) ) {
|
||||||
$type_label = strtoupper( $type_terms[0]->name );
|
$type_label = strtoupper( $type_terms[0]->name );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$is_website = Projects::is_website( $project_id );
|
||||||
|
$site_url = Projects::get_site_url( $project_id );
|
||||||
|
$site_platform = Projects::get_site_platform( $project_id );
|
||||||
$release_url = Projects::get_release_url( $project_id );
|
$release_url = Projects::get_release_url( $project_id );
|
||||||
$repo_url = Projects::get_repo_browse_url( $project_id );
|
$repo_url = Projects::get_repo_browse_url( $project_id );
|
||||||
$repo_data = Projects::get_repo_data( $project_id );
|
$repo_data = Projects::get_repo_data( $project_id );
|
||||||
$language = is_array( $repo_data ) && ! empty( $repo_data['language'] ) ? (string) $repo_data['language'] : '';
|
$language = is_array( $repo_data ) && isset( $repo_data['stargazers_count'] ) ? (int) $repo_data['stargazers_count'] : 0;
|
||||||
$stars = is_array( $repo_data ) && isset( $repo_data['stargazers_count'] ) ? (int) $repo_data['stargazers_count'] : 0;
|
|
||||||
$excerpt = has_excerpt( $project_id ) ? get_the_excerpt( $project_id ) : wp_trim_words( wp_strip_all_tags( $post->post_content ), 30, '…' );
|
$excerpt = has_excerpt( $project_id ) ? get_the_excerpt( $project_id ) : wp_trim_words( wp_strip_all_tags( $post->post_content ), 30, '…' );
|
||||||
$card_classes = 'project-card group';
|
$card_classes = 'project-card group';
|
||||||
?>
|
?>
|
||||||
@@ -34,8 +38,9 @@ $card_classes = 'project-card group';
|
|||||||
<?php if ( $type_label ) : ?>
|
<?php if ( $type_label ) : ?>
|
||||||
<span class="project-card__type"><?php echo esc_html( $type_label ); ?></span>
|
<span class="project-card__type"><?php echo esc_html( $type_label ); ?></span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<span class="project-card__provider" aria-hidden="true">
|
<span class="project-card__provider" aria-hidden="true">
|
||||||
<?php echo esc_html( Projects::get_provider_label( $project_id ) ); ?>
|
<?php echo esc_html( $is_website ? __( 'Website', 'ks-portfolio' ) : Projects::get_provider_label( $project_id ) ); ?>
|
||||||
</span>
|
</span>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -50,28 +55,42 @@ $card_classes = 'project-card group';
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<footer class="project-card__foot">
|
<footer class="project-card__foot">
|
||||||
<?php if ( $language || $stars > 0 ) : ?>
|
<?php if ( ! $is_website && ( $language || $stars > 0 ) ) : ?>
|
||||||
<div class="project-card__meta">
|
<div class="project-card__meta">
|
||||||
<?php if ( $language ) : ?>
|
<?php if ( $language ) : ?>
|
||||||
<span class="project-card__lang"><?php echo esc_html( $language ); ?></span>
|
<span class="project-card__lang"><?php echo esc_html( $language ); ?></span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ( $stars > 0 ) : ?>
|
<?php if ( $stars > 0 ) : ?>
|
||||||
<span class="project-card__stars" aria-label="<?php echo esc_attr( $stars . ' stars' ); ?>">★ <?php echo esc_html( (string) $stars ); ?></span>
|
<span class="project-card__stars" aria-label="<?php echo esc_attr( $stars . ' stars' ); ?>">★ <?php echo esc_html( (string) $stars ); ?></span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
<?php elseif ( $is_website && $site_platform ) : ?>
|
||||||
|
<div class="project-card__meta">
|
||||||
|
<span class="project-card__platform"><?php echo esc_html( $site_platform ); ?></span>
|
||||||
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="project-card__actions">
|
<div class="project-card__actions">
|
||||||
|
<?php if ( $is_website ) : ?>
|
||||||
|
<?php if ( $site_url ) : ?>
|
||||||
|
<a class="project-card__action project-card__action--primary" href="<?php echo esc_url( $site_url ); ?>" target="_blank" rel="noopener noreferrer">
|
||||||
|
<?php echo esc_html__( 'Visit Site', 'ks-portfolio' ); ?>
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php else : ?>
|
||||||
<?php if ( $release_url ) : ?>
|
<?php if ( $release_url ) : ?>
|
||||||
<a class="project-card__action project-card__action--primary" href="<?php echo esc_url( Projects::get_download_url( $project_id ) ); ?>" rel="nofollow">
|
<a class="project-card__action project-card__action--primary" href="<?php echo esc_url( Projects::get_download_url( $project_id ) ); ?>" rel="nofollow">
|
||||||
<?php echo esc_html__( 'Download', 'ks-portfolio' ); ?>
|
<?php echo esc_html__( 'Download', 'ks-portfolio' ); ?>
|
||||||
</a>
|
</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ( $repo_url ) : ?>
|
<?php if ( $repo_url ) : ?>
|
||||||
<a class="project-card__action project-card__action--secondary" href="<?php echo esc_url( $repo_url ); ?>" target="_blank" rel="noopener noreferrer">
|
<a class="project-card__action project-card__action--secondary" href="<?php echo esc_url( $repo_url ); ?>" target="_blank" rel="noopener noreferrer">
|
||||||
<?php echo esc_html__( 'View Repo', 'ks-portfolio' ); ?>
|
<?php echo esc_html__( 'View Repo', 'ks-portfolio' ); ?>
|
||||||
</a>
|
</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -23,10 +23,12 @@ if ( ! isset( $stat_label ) || ! isset( $stat_value ) ) {
|
|||||||
$stat_label = (string) $stat_label;
|
$stat_label = (string) $stat_label;
|
||||||
$stat_value = (string) $stat_value;
|
$stat_value = (string) $stat_value;
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="stat-card" role="group" aria-label="<?php echo esc_attr( $stat_label ); ?>">
|
<div class="stat-card" role="group" aria-label="<?php echo esc_attr( $stat_label ); ?>">
|
||||||
<?php if ( ! empty( $icon ) ) : ?>
|
<?php if ( ! empty( $icon ) ) : ?>
|
||||||
<span class="stat-card__icon" aria-hidden="true"><?php echo $icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- icon is hardcoded decorative SVG in callers. ?></span>
|
<span class="stat-card__icon" aria-hidden="true"><?php echo $icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- icon is hardcoded decorative SVG in callers. ?></span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<span class="stat-card__value"><?php echo esc_html( $stat_value ); ?></span>
|
<span class="stat-card__value"><?php echo esc_html( $stat_value ); ?></span>
|
||||||
<span class="stat-card__label"><?php echo esc_html( $stat_label ); ?></span>
|
<span class="stat-card__label"><?php echo esc_html( $stat_label ); ?></span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg viewBox="0 0 24 24" width="1.5rem" height="1.5rem" role="img" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path d="M4.209 4.603c-.247 0-.525.02-.84.088-.333.07-1.28.283-2.054 1.027C-.403 7.25.035 9.685.089 10.052c.065.446.263 1.687 1.21 2.768 1.749 2.141 5.513 2.092 5.513 2.092s.462 1.103 1.168 2.119c.955 1.263 1.936 2.248 2.89 2.367 2.406 0 7.212-.004 7.212-.004s.458.004 1.08-.394c.535-.324 1.013-.893 1.013-.893s.492-.527 1.18-1.73c.21-.37.385-.729.538-1.068 0 0 2.107-4.471 2.107-8.823-.042-1.318-.367-1.55-.443-1.627-.156-.156-.366-.153-.366-.153s-4.475.252-6.792.306c-.508.011-1.012.023-1.512.027v4.474l-.634-.301c0-1.39-.004-4.17-.004-4.17-1.107.016-3.405-.084-3.405-.084s-5.399-.27-5.987-.324c-.187-.011-.401-.032-.648-.032zm.354 1.832h.111s.271 2.269.6 3.597C5.549 11.147 6.22 13 6.22 13s-.996-.119-1.641-.348c-.99-.324-1.409-.714-1.409-.714s-.73-.511-1.096-1.52C1.444 8.73 2.021 7.7 2.021 7.7s.32-.859 1.47-1.145c.395-.106.863-.12 1.072-.12zm8.33 2.554c.26.003.509.127.509.127l.868.422-.529 1.075a.686.686 0 0 0-.614.359.685.685 0 0 0 .072.756l-.939 1.924a.69.69 0 0 0-.66.527.687.687 0 0 0 .347.763.686.686 0 0 0 .867-.206.688.688 0 0 0-.069-.882l.916-1.874a.667.667 0 0 0 .237-.02.657.657 0 0 0 .271-.137 8.826 8.826 0 0 1 1.016.512.761.761 0 0 1 .286.282c.073.21-.073.569-.073.569-.087.29-.702 1.55-.702 1.55a.692.692 0 0 0-.676.477.681.681 0 1 0 1.157-.252c.073-.141.141-.282.214-.431.19-.397.515-1.16.515-1.16.035-.066.218-.394.103-.814-.095-.435-.48-.638-.48-.638-.467-.301-1.116-.58-1.116-.58s0-.156-.042-.27a.688.688 0 0 0-.148-.241l.516-1.062 2.89 1.401s.48.218.583.619c.073.282-.019.534-.069.657-.24.587-2.1 4.317-2.1 4.317s-.232.554-.748.588a1.065 1.065 0 0 1-.393-.045l-.202-.08-4.31-2.1s-.417-.218-.49-.596c-.083-.31.104-.691.104-.691l2.073-4.272s.183-.37.466-.497a.855.855 0 0 1 .35-.077z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<i class="icon-github"></i>
|
||||||
@@ -11,30 +11,64 @@ $args = $args ?? array();
|
|||||||
$classes = $args['classes'] ?? '';
|
$classes = $args['classes'] ?? '';
|
||||||
$circle = $args['circle'] ?? '';
|
$circle = $args['circle'] ?? '';
|
||||||
|
|
||||||
// Define social media sites and their URLs
|
// Get social media repeater data
|
||||||
$sites = array(
|
$social_media_items = get_field( 'social_media', 'option' );
|
||||||
'facebook' => getFieldValue( 'social_media.facebook' ) ? getFieldValue( 'social_media.facebook' ) : '',
|
|
||||||
'twitter' => getFieldValue( 'social_media.twitter' ) ? getFieldValue( 'social_media.twitter' ) : '',
|
|
||||||
'pinterest' => getFieldValue( 'social_media.pinterest' ) ? getFieldValue( 'social_media.pinterest' ) : '',
|
|
||||||
'instagram' => getFieldValue( 'social_media.instagram' ) ? getFieldValue( 'social_media.instagram' ) : '',
|
|
||||||
'youtube' => getFieldValue( 'social_media.youtube' ) ? getFieldValue( 'social_media.youtube' ) : '',
|
|
||||||
'linkedin' => getFieldValue( 'social_media.linkedin' ) ? getFieldValue( 'social_media.linkedin' ) : '',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add circle class if the circle option is enabled
|
// Add circle class if the circle option is enabled
|
||||||
if ( $circle ) {
|
if ( $circle ) {
|
||||||
$classes .= ' circular-icon';
|
$classes .= ' circular-icon';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop through the social media sites and output links
|
/**
|
||||||
foreach ( $sites as $name => $url ) {
|
* Detect social media service from URL
|
||||||
|
*
|
||||||
|
* @param string $url The social media URL.
|
||||||
|
* @return string The service name
|
||||||
|
*/
|
||||||
|
function detect_social_service( $url ) {
|
||||||
|
$domain = wp_parse_url( $url, PHP_URL_HOST );
|
||||||
|
$domain = strtolower( $domain );
|
||||||
|
|
||||||
|
// Define domain patterns for each service
|
||||||
|
$services = array(
|
||||||
|
'facebook' => array( 'facebook.com', 'fb.com' ),
|
||||||
|
'instagram' => array( 'instagram.com' ),
|
||||||
|
'twitter' => array( 'twitter.com', 'x.com' ),
|
||||||
|
'linkedin' => array( 'linkedin.com' ),
|
||||||
|
'github' => array( 'github.com' ),
|
||||||
|
'youtube' => array( 'youtube.com', 'youtu.be' ),
|
||||||
|
'pinterest' => array( 'pinterest.com' ),
|
||||||
|
'gitea' => array( 'gitea.com', 'keithsolomon.net', 'codeberg.org' ),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ( $services as $service => $domains ) {
|
||||||
|
foreach ( $domains as $service_domain ) {
|
||||||
|
if ( strpos( $domain, $service_domain ) !== false ) {
|
||||||
|
return $service;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through the social media repeater items and output links
|
||||||
|
if ( $social_media_items && is_array( $social_media_items ) ) {
|
||||||
|
foreach ( $social_media_items as $item ) {
|
||||||
|
$url = $item['url'] ?? '';
|
||||||
|
|
||||||
if ( $url ) {
|
if ( $url ) {
|
||||||
|
$service = detect_social_service( $url );
|
||||||
|
|
||||||
|
if ( $service ) {
|
||||||
?>
|
?>
|
||||||
<a href="<?php echo esc_url( $url ); ?>" class="<?php echo esc_attr( $classes ); ?>">
|
<a href="<?php echo esc_url( $url ); ?>" class="<?php echo esc_attr( $classes ); ?>">
|
||||||
<?php get_template_part( 'views/icons/' . $name ); ?>
|
<?php get_template_part( 'views/icons/' . $service ); ?>
|
||||||
<span class="sr-only bg-white text-black">Visit our <?php echo esc_html( $name ); ?> page</span>
|
<span class="sr-only bg-white text-black">Visit my <?php echo esc_html( $service ); ?> page</span>
|
||||||
</a>
|
</a>
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
|
|||||||
Reference in New Issue
Block a user