diff --git a/lib/class-projects.php b/lib/class-projects.php index 8b5418f..f34c740 100644 --- a/lib/class-projects.php +++ b/lib/class-projects.php @@ -139,4 +139,57 @@ class Projects { } 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 ); + } } diff --git a/single-projects.php b/single-projects.php index 3006abb..1abe8ee 100644 --- a/single-projects.php +++ b/single-projects.php @@ -16,16 +16,20 @@ get_header(); while ( have_posts() ) : the_post(); - $project_id = (int) get_the_ID(); - $provider = Projects::get_provider_label( $project_id ); - $version = Projects::get_latest_version( $project_id ); - $release = Projects::get_release_url( $project_id ); - $repo = Projects::get_repo_browse_url( $project_id ); - $repo_data = Projects::get_repo_data( $project_id ); - $owner = Projects::get_owner_data( $project_id ); - $type_terms = get_the_terms( $project_id, 'project-type' ); - $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 ) : ''; + $project_id = (int) get_the_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 ); + $release = Projects::get_release_url( $project_id ); + $repo = Projects::get_repo_browse_url( $project_id ); + $repo_data = Projects::get_repo_data( $project_id ); + $owner = Projects::get_owner_data( $project_id ); + $type_terms = get_the_terms( $project_id, 'project-type' ); + $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 ) : ''; ?>
@@ -39,7 +43,7 @@ while ( have_posts() ) :

- + v

@@ -48,25 +52,39 @@ while ( have_posts() ) :
- - - - - v - - - - - - - + + + + + + + + + + + + v + + + + + + + +
- + +
+ + <?php echo esc_attr( get_the_title( $project_id ) ); ?> + +
+ @@ -78,83 +96,96 @@ while ( have_posts() ) :
-
diff --git a/styles/base/colors.css b/styles/base/colors.css index 8357a21..71d246b 100644 --- a/styles/base/colors.css +++ b/styles/base/colors.css @@ -1,6 +1,6 @@ /* Theme color definitions */ -@theme { +@theme static { --color-black: oklch(0% 0 0); --color-white: oklch(100% 0 0); @@ -38,32 +38,32 @@ --color-dark: oklch(34.51% 0.0133 248.2); /* Carbon Blue / dark surface tokens (Portfolio 2026) */ - --color-surface: #121318; - --color-surface-dim: #121318; - --color-surface-bright: #38393f; - --color-surface-container-lowest: #0d0e13; - --color-surface-container-low: #1a1b21; - --color-surface-container: #1e1f25; - --color-surface-container-high: #292a2f; - --color-surface-container-highest: #34343a; - --color-on-surface: #e3e1e9; - --color-on-surface-variant: #c5c5d3; - --color-outline: #8f909d; - --color-outline-variant: #444651; - --color-page-base: #100e0b; - --color-backlight: rgba(96, 165, 250, 0.20); + --color-surface: oklch(18.79% 0.0103 276.4); + --color-surface-dim: oklch(18.79% 0.0103 276.4); + --color-surface-bright: oklch(34.59% 0.0105 278.3); + --color-surface-container-lowest: oklch(16.51% 0.0106 276.3); + --color-surface-container-low: oklch(22.37% 0.0117 277.9); + --color-surface-container: oklch(24.08% 0.0115 278); + --color-surface-container-high: oklch(28.6% 0.0092 276.8); + --color-surface-container-highest: oklch(32.74% 0.0105 285.8); + --color-on-surface: oklch(91.36% 0.011 297.6); + --color-on-surface-variant: oklch(82.77% 0.0194 286); + --color-outline: oklch(65.68% 0.0192 282.3); + --color-outline-variant: oklch(39.65% 0.0187 277.4); + --color-page-base: oklch(16.5% 0.007 78.1); + --color-backlight: oklch(71.37% 0.1434 254.6 / 0.2); - --color-primary-fixed: #dce1ff; - --color-primary-fixed-dim: #b6c4ff; - --color-on-primary: #05297a; - --color-on-primary-fixed: #00164e; - --color-on-primary-fixed-variant: #264191; + --color-primary-fixed: oklch(91.52% 0.0412 278.1); + --color-primary-fixed-dim: oklch(83.03% 0.0845 274.1); + --color-on-primary: oklch(32.02% 0.1432 262.8); + --color-on-primary-fixed: oklch(23.06% 0.1072 262.1); + --color-on-primary-fixed-variant: oklch(40.35% 0.1363 266.3); - --color-primary-12: rgba(182, 196, 255, 0.12); - --color-primary-87: #dce1ff; + --color-primary-12: oklch(83.03% 0.0845 274.1 / 0.12); + --color-primary-87: oklch(91.52% 0.0412 278.1); - --color-success: #5ccf91; - --color-warning: #f4e04d; - --color-danger: #cc3b3b; - --color-info: #3d7ab0; + --color-success: oklch(77.17% 0.1386 157.2); + --color-warning: oklch(91.37% 0.1234 97.2); + --color-danger: oklch(54.12% 0.1456 29.8); + --color-info: oklch(45.67% 0.1345 210.3); } diff --git a/styles/base/typography.css b/styles/base/typography.css index 641c034..55a9518 100644 --- a/styles/base/typography.css +++ b/styles/base/typography.css @@ -39,7 +39,7 @@ * 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-headings: var(--font-sans); --line-height: 1.6; @@ -140,10 +140,15 @@ p { 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; } @@ -169,7 +174,7 @@ pre code { padding: 0; } -code { @apply bg-black/30 px-0.25 py-0.5 font-mono text-black text-xs rounded-sm; } +code { @apply bg-white/30 px-0.25 py-0.25 font-mono text-black rounded-sm; } hr { background-color: black; diff --git a/styles/blocks/buttons.css b/styles/blocks/buttons.css index 5000e20..f729af2 100644 --- a/styles/blocks/buttons.css +++ b/styles/blocks/buttons.css @@ -1,95 +1,117 @@ /* Button styles */ -@theme { +@theme inline { /* Configuration */ - /** - * Button component settings - * - * The variables below are used to define the button styles. - * Most of these variables come from the main theme configuration. - * The following variables are not defined, but have fallback values - * in their respective locations and can be added if needed: - * - * --button-font-weight (fallback is 600/semibold) - * --button-font-size (fallback is 1rem) - * --button-outline-width (fallback is --button-border-width) - * --button-outline-style (fallback is --button-border-style) - * --button-outline-color (fallback is --button-border-color) - */ + /** + * Button component settings + * + * The variables below are used to define the button styles. + * Most of these variables come from the main theme configuration. + * The following variables are not defined, but have fallback values + * in their respective locations and can be added if needed: + * + * --button-font-weight (fallback is 600/semibold) + * --button-font-size (fallback is 1rem) + * --button-outline-width (fallback is --button-border-width) + * --button-outline-style (fallback is --button-border-style) + * --button-outline-color (fallback is --button-border-color) + */ - --button-bg: var(--color-primary); - --button-color: var(--color-white); - --button-hover-bg: var(--color-info); - --button-hover-border-color: var(--color-info); - --button-hover-color: var(--color-white); - --button-border-width: 3px; - --button-border-style: solid; - --button-border-color: var(--button-bg); - --button-radius: 0.5rem; + --button-bg: var(--color-on-primary-fixed-variant); + --button-color: var(--color-primary-fixed-dim); + --button-hover-bg: var(--color-info); + --button-hover-border-color: var(--color-info); + --button-hover-color: var(--color-white); + --button-border-width: 3px; + --button-border-style: solid; + --button-border-color: var(--color-primary-fixed-dim); + --button-radius: 0.5rem; } .btn, .button, .acf-block-preview .button { - @apply px-8 py-2 min-w-0; + @apply px-8 py-2 min-w-0; - background: var(--button-bg); - color: var(--button-color); - cursor: pointer; - display: inline-block; - transition: background 200ms, border-color 200ms, color 200ms; + background: var(--button-bg); + color: var(--button-color); + cursor: pointer; + display: inline-block; + transition: background 200ms, border-color 200ms, color 200ms; - border-width: var(--button-border-width); - border-style: var(--button-border-style); - border-color: var(--button-border-color); - border-radius: var(--button-radius); + border-width: var(--button-border-width); + border-style: var(--button-border-style); + border-color: var(--button-border-color); + border-radius: var(--button-radius); - text-decoration: none; - font-weight: var(--button-font-weight, 600); - font-size: var(--button-font-size, 1rem); - line-height: 1.1; + text-decoration: none; + font-weight: var(--button-font-weight, 600); + font-size: var(--button-font-size, 1rem); + line-height: 1.1; - &[data-button-variant="outline"] { - background: transparent; - &:hover { background: transparent; } + &[data-button-variant="outline"] { + background: transparent; + color: var(--button-border-color); + border-color: var(--button-bg); - --button-color: var(--button-outline-color, var(--button-bg)); - --button-hover-border-color: var(--button-hover-bg); - --button-hover-color: var(--button-hover-bg); - } + &:hover { + background: transparent; + border-color: var(--button-hover-bg); + color: var(--button-hover-color); + } + } } /* Hover/focus/active */ .btn:hover, .button:hover, .acf-block-preview .button:hover { - background: var(--button-hover-bg); - border-color: var(--button-hover-border-color); - color: var(--button-hover-color); + background: var(--button-hover-bg); + border-color: var(--button-hover-border-color); + color: var(--button-hover-color); } .btn:focus, .button:focus { - outline-width: var(--button-outline-width, var(--button-border-width)); - outline-style: var(--button-outline-style, var(--button-border-style)); - outline-color: var(--button-outline-color, var(--button-border-color)); - outline-offset: calc(var(--button-border-width) * 2); + outline-width: var(--button-outline-width, var(--button-border-width)); + outline-style: var(--button-outline-style, var(--button-border-style)); + outline-color: var(--button-outline-color, var(--button-border-color)); + outline-offset: calc(var(--button-border-width) * 2); } .btn:active, .button:active { transform: scale(99%); } /* Back To Top Button */ #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); - color:#fff; - border:none; - box-shadow:0 2px 8px rgba(0,0,0,0.15); - cursor:pointer; - transition:opacity 0.2s; + background: var(--color-primary,#3857BC); + border: none; + border-radius: 2em; + bottom: 2rem; + box-shadow: 0 2px 8px rgba(0,0,0,0.15); + color: #fff; + cursor: pointer; + display: none; + font-size: 1.1rem; + padding: 0.75em 1.5em; + position: fixed; + right: 2rem; + 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); + } } /** @@ -121,58 +143,40 @@ x-button:has(.button[data-button-width="full"]) { @apply w-full; } * like the ones below and changing the color values. */ .btn[data-button-color="secondary"], .button[data-button-color="secondary"] { - --button-bg: var(--color-secondary); - --button-color: var(--color-white); - --button-border-color: var(--color-secondary); - --button-outline-color: oklch(0.48 0.0136 252.2); - --button-hover-bg: var(--color-dark); - --button-hover-border-color: var(--color-dark); - --button-hover-color: var(--color-white); + --button-bg: var(--color-secondary); + --button-color: var(--color-white); + --button-border-color: var(--color-secondary); + --button-outline-color: oklch(0.48 0.0136 252.2); + --button-hover-bg: var(--color-dark); + --button-hover-border-color: var(--color-dark); + --button-hover-color: var(--color-white); } .btn[data-button-color="light"], .button[data-button-color="light"] { - --button-bg: var(--color-white); - --button-color: var(--color-dark); - --button-border-color: var(--color-white); - --button-hover-bg: var(--color-dark); - --button-hover-border-color: var(--color-dark); - --button-hover-color: var(--color-white); + --button-bg: var(--color-white); + --button-color: var(--color-dark); + --button-border-color: var(--color-white); + --button-hover-bg: var(--color-dark); + --button-hover-border-color: var(--color-dark); + --button-hover-color: var(--color-white); } .btn[data-button-color="white"], .button[data-button-color="white"] { - --button-bg: var(--color-white); - --button-color: var(--color-black); - --button-border-color: var(--color-white); - --button-hover-bg: var(--color-secondary-200); - --button-hover-border-color: var(--color-secondary-200); - --button-hover-color: var(--color-black); + --button-bg: var(--color-white); + --button-color: var(--color-black); + --button-border-color: var(--color-white); + --button-hover-bg: var(--color-secondary-200); + --button-hover-border-color: var(--color-secondary-200); + --button-hover-color: var(--color-black); } .btn[data-button-color="black"], .button[data-button-color="black"] { - --button-bg: var(--color-black); - --button-color: var(--color-white); - --button-border-color: var(--color-black); - --button-hover-bg: var(--color-secondary); - --button-hover-border-color: var(--color-secondary); - --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); + --button-bg: var(--color-black); + --button-color: var(--color-white); + --button-border-color: var(--color-black); + --button-hover-bg: var(--color-secondary); + --button-hover-border-color: var(--color-secondary); + --button-hover-color: var(--color-dark); } /* Dark-surface button variants (WCAG-AA against #100e0b) */ @@ -191,6 +195,7 @@ x-button:has(.button[data-button-width="full"]) { @apply w-full; } } .button:active { transform: scale(0.99); } + @media (prefers-reduced-motion: reduce) { .button:active { transform: none; } } .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); color: var(--color-on-primary, #05297a); 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 { background: transparent; - color: var(--color-on-surface, #e3e1e9); 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 { - background: var(--color-surface-container-high, #292a2f); - color: var(--color-on-surface, #e3e1e9); + background: var(--color-surface-container-high, #292a2f); 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 @@ -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 * dark and drops to 2.81:1, so we go to --color-white instead. */ .site-header .button--outline { - color: var(--color-white, #ffffff); border-color: var(--color-white, #ffffff); -} -.site-header .button--outline:hover { - color: var(--color-primary-87, #dce1ff); - border-color: var(--color-primary-87, #dce1ff); + color: var(--color-white, #ffffff); + + &:hover { + border-color: var(--color-primary-87, #dce1ff); + color : var(--color-primary-87, #dce1ff); + } } diff --git a/styles/components/project-card.css b/styles/components/project-card.css index cf6f7ba..8c694ad 100644 --- a/styles/components/project-card.css +++ b/styles/components/project-card.css @@ -66,6 +66,7 @@ margin: 0; display: -webkit-box; -webkit-line-clamp: 4; + line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; } @@ -96,23 +97,22 @@ text-decoration: none; border: 2px solid transparent; 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 { - background: var(--color-primary, #b6c4ff); - color: var(--color-on-primary, #05297a); + background: var(--color-primary); + color: #fff; 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 { color: var(--color-on-surface, #e3e1e9); 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 { outline: 2px solid var(--color-primary, #b6c4ff); outline-offset: 3px; @@ -327,3 +327,9 @@ } .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; } + diff --git a/views/components/project-card.php b/views/components/project-card.php index 3436c1e..b93063c 100644 --- a/views/components/project-card.php +++ b/views/components/project-card.php @@ -20,6 +20,9 @@ $type_terms = get_the_terms( $project_id, 'project-type' ); if ( ! empty( $type_terms ) && ! is_wp_error( $type_terms ) ) { $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 ); $repo_url = Projects::get_repo_browse_url( $project_id ); $repo_data = Projects::get_repo_data( $project_id ); @@ -35,7 +38,7 @@ $card_classes = 'project-card group'; @@ -50,7 +53,7 @@ $card_classes = 'project-card group';