Initial commit: gustavoo portfolio project
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: Gustavo Portfolio Core
|
||||
* Plugin URI: https://gustavoo.me/
|
||||
* Description: Conteúdo, configurações e integrações do portfólio de Gustavo Oliveira.
|
||||
* Version: 1.6.0
|
||||
* Requires at least: 6.5
|
||||
* Requires PHP: 8.0
|
||||
* Requires Plugins: fluentform, fluent-crm, fluent-smtp
|
||||
* Author: Gustavo Oliveira
|
||||
* Author URI: https://gustavoo.me/
|
||||
* Text Domain: gustavoo-portfolio-core
|
||||
* License: GPL-2.0-or-later
|
||||
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
*
|
||||
* @package GustavoPortfolioCore
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
define( 'GSO_PORTFOLIO_CORE_VERSION', '1.6.0' );
|
||||
define( 'GSO_PORTFOLIO_CORE_FILE', __FILE__ );
|
||||
define( 'GSO_PORTFOLIO_CORE_DIR', plugin_dir_path( __FILE__ ) );
|
||||
|
||||
require_once GSO_PORTFOLIO_CORE_DIR . 'includes/class-gso-projects.php';
|
||||
require_once GSO_PORTFOLIO_CORE_DIR . 'includes/class-gso-portfolio-settings.php';
|
||||
require_once GSO_PORTFOLIO_CORE_DIR . 'includes/class-gso-newsletter-widget.php';
|
||||
require_once GSO_PORTFOLIO_CORE_DIR . 'includes/class-gso-seeder.php';
|
||||
require_once GSO_PORTFOLIO_CORE_DIR . 'includes/class-gso-portfolio-core.php';
|
||||
|
||||
register_activation_hook( __FILE__, array( 'GSO_Portfolio_Core', 'activate' ) );
|
||||
register_deactivation_hook( __FILE__, array( 'GSO_Portfolio_Core', 'deactivate' ) );
|
||||
|
||||
GSO_Portfolio_Core::init();
|
||||
|
||||
if ( ! function_exists( 'gso_portfolio_get_settings' ) ) {
|
||||
/**
|
||||
* Return all portfolio settings merged with defaults.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
function gso_portfolio_get_settings() {
|
||||
return GSO_Portfolio_Settings::get_settings();
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'gso_portfolio_get_setting' ) ) {
|
||||
/**
|
||||
* Return one portfolio setting.
|
||||
*
|
||||
* @param string $key Setting key.
|
||||
* @param mixed $default Fallback value.
|
||||
* @return mixed
|
||||
*/
|
||||
function gso_portfolio_get_setting( $key, $default = null ) {
|
||||
$settings = gso_portfolio_get_settings();
|
||||
|
||||
return array_key_exists( $key, $settings ) ? $settings[ $key ] : $default;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'gso_render_fluent_form' ) ) {
|
||||
/**
|
||||
* Render a Fluent Form using a trusted numeric ID.
|
||||
*
|
||||
* @param int $form_id Fluent Forms form ID.
|
||||
* @return string
|
||||
*/
|
||||
function gso_render_fluent_form( $form_id ) {
|
||||
$form_id = absint( $form_id );
|
||||
|
||||
if ( ! $form_id || ! shortcode_exists( 'fluentform' ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return do_shortcode( sprintf( '[fluentform id="%d"]', $form_id ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'gso_portfolio_render_fluent_form' ) ) {
|
||||
/**
|
||||
* Backwards-compatible descriptive alias for the form helper.
|
||||
*
|
||||
* @param int $form_id Fluent Forms form ID.
|
||||
* @return string
|
||||
*/
|
||||
function gso_portfolio_render_fluent_form( $form_id ) {
|
||||
return gso_render_fluent_form( $form_id );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
/**
|
||||
* Newsletter widget powered by Fluent Forms and FluentCRM.
|
||||
*
|
||||
* @package GustavoPortfolioCore
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Keeps the newsletter CTA available even if the presentation theme changes.
|
||||
*/
|
||||
final class GSO_Newsletter_Widget extends WP_Widget {
|
||||
/**
|
||||
* Register the widget.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'gso_newsletter_widget',
|
||||
__( 'Gustavo — Newsletter (FluentCRM)', 'gustavoo-portfolio-core' ),
|
||||
array(
|
||||
'classname' => 'widget_gso_newsletter',
|
||||
'description' => __( 'Exibe o formulário global de newsletter configurado no Portfólio.', 'gustavoo-portfolio-core' ),
|
||||
'customize_selective_refresh' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the widget.
|
||||
*
|
||||
* @param array<string, string> $args Sidebar wrappers.
|
||||
* @param array<string, mixed> $instance Saved settings.
|
||||
* @return void
|
||||
*/
|
||||
public function widget( $args, $instance ) {
|
||||
$settings = GSO_Portfolio_Settings::get_settings();
|
||||
$title = ! empty( $instance['title'] ) ? (string) $instance['title'] : (string) $settings['newsletter_heading'];
|
||||
$text = ! empty( $instance['text'] ) ? (string) $instance['text'] : (string) $settings['newsletter_text'];
|
||||
$form_id = ! empty( $instance['form_id'] ) ? absint( $instance['form_id'] ) : absint( $settings['newsletter_form_id'] );
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Registered sidebar wrapper.
|
||||
|
||||
if ( $title ) {
|
||||
echo $args['before_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Registered sidebar wrapper.
|
||||
echo esc_html( $title );
|
||||
echo $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Registered sidebar wrapper.
|
||||
}
|
||||
|
||||
if ( $text ) {
|
||||
printf( '<p class="widget_gso_newsletter__text">%s</p>', esc_html( $text ) );
|
||||
}
|
||||
|
||||
$form = gso_render_fluent_form( $form_id );
|
||||
if ( $form ) {
|
||||
echo $form; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is generated by Fluent Forms.
|
||||
} elseif ( current_user_can( 'manage_options' ) ) {
|
||||
printf(
|
||||
'<p><a href="%1$s">%2$s</a></p>',
|
||||
esc_url( admin_url( 'edit.php?post_type=' . GSO_Projects::POST_TYPE . '&page=' . GSO_Portfolio_Settings::PAGE_SLUG ) ),
|
||||
esc_html__( 'Configure o formulário de newsletter.', 'gustavoo-portfolio-core' )
|
||||
);
|
||||
}
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Registered sidebar wrapper.
|
||||
}
|
||||
|
||||
/**
|
||||
* Render widget settings.
|
||||
*
|
||||
* @param array<string, mixed> $instance Saved settings.
|
||||
* @return void
|
||||
*/
|
||||
public function form( $instance ) {
|
||||
$title = isset( $instance['title'] ) ? (string) $instance['title'] : '';
|
||||
$text = isset( $instance['text'] ) ? (string) $instance['text'] : '';
|
||||
$form_id = isset( $instance['form_id'] ) ? absint( $instance['form_id'] ) : 0;
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Título:', 'gustavoo-portfolio-core' ); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>"><?php esc_html_e( 'Texto:', 'gustavoo-portfolio-core' ); ?></label>
|
||||
<textarea class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'text' ) ); ?>" rows="4"><?php echo esc_textarea( $text ); ?></textarea>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr( $this->get_field_id( 'form_id' ) ); ?>"><?php esc_html_e( 'ID do Fluent Forms (opcional):', 'gustavoo-portfolio-core' ); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr( $this->get_field_id( 'form_id' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'form_id' ) ); ?>" type="number" min="0" step="1" value="<?php echo esc_attr( (string) $form_id ); ?>">
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize widget settings.
|
||||
*
|
||||
* @param array<string, mixed> $new_instance New values.
|
||||
* @param array<string, mixed> $old_instance Previous values.
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function update( $new_instance, $old_instance ) {
|
||||
unset( $old_instance );
|
||||
|
||||
return array(
|
||||
'title' => isset( $new_instance['title'] ) ? sanitize_text_field( $new_instance['title'] ) : '',
|
||||
'text' => isset( $new_instance['text'] ) ? sanitize_textarea_field( $new_instance['text'] ) : '',
|
||||
'form_id' => isset( $new_instance['form_id'] ) ? absint( $new_instance['form_id'] ) : 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/**
|
||||
* Main plugin coordinator.
|
||||
*
|
||||
* @package GustavoPortfolioCore
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Coordinates the plugin modules.
|
||||
*/
|
||||
final class GSO_Portfolio_Core {
|
||||
/**
|
||||
* Whether hooks have already been registered.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private static $initialized = false;
|
||||
|
||||
/**
|
||||
* Register plugin hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
if ( self::$initialized ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$initialized = true;
|
||||
|
||||
add_action( 'plugins_loaded', array( __CLASS__, 'load_textdomain' ) );
|
||||
|
||||
GSO_Projects::init();
|
||||
GSO_Portfolio_Settings::init();
|
||||
GSO_Seeder::init();
|
||||
|
||||
add_action( 'widgets_init', array( __CLASS__, 'register_widgets' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load translations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function load_textdomain() {
|
||||
load_plugin_textdomain(
|
||||
'gustavoo-portfolio-core',
|
||||
false,
|
||||
dirname( plugin_basename( GSO_PORTFOLIO_CORE_FILE ) ) . '/languages'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register bundled widgets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_widgets() {
|
||||
if ( class_exists( 'Gustavao_Portfolio_Newsletter_Widget' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
register_widget( 'GSO_Newsletter_Widget' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Run activation tasks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function activate() {
|
||||
GSO_Projects::register_content_types();
|
||||
GSO_Portfolio_Settings::ensure_defaults();
|
||||
GSO_Seeder::activate();
|
||||
|
||||
flush_rewrite_rules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh rewrite rules without deleting content or settings.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function deactivate() {
|
||||
flush_rewrite_rules();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,459 @@
|
||||
<?php
|
||||
/**
|
||||
* Portfolio settings screen and option contract.
|
||||
*
|
||||
* @package GustavoPortfolioCore
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Owns the shared option consumed by the portfolio theme.
|
||||
*/
|
||||
final class GSO_Portfolio_Settings {
|
||||
const OPTION_NAME = 'gustavoo_portfolio_settings';
|
||||
const OPTION_GROUP = 'gustavoo_portfolio_settings_group';
|
||||
const PAGE_SLUG = 'gso-portfolio-settings';
|
||||
|
||||
/**
|
||||
* Register hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'admin_menu', array( __CLASS__, 'add_settings_page' ), 20 );
|
||||
add_action( 'admin_init', array( __CLASS__, 'register_settings' ) );
|
||||
add_filter( 'plugin_action_links_' . plugin_basename( GSO_PORTFOLIO_CORE_FILE ), array( __CLASS__, 'plugin_action_links' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults based on the existing gustavoo.me content.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function defaults() {
|
||||
return array(
|
||||
'hero_eyebrow' => __( 'Desenvolvimento de ponta a ponta', 'gustavoo-portfolio-core' ),
|
||||
'hero_title' => __( 'Transformo ideias em produtos digitais sólidos.', 'gustavoo-portfolio-core' ),
|
||||
'hero_description' => __( 'Gustavo, Desenvolvedor Full Stack Sênior. Web, mobile, infraestrutura e automação para tirar projetos do papel e fazê-los crescer.', 'gustavoo-portfolio-core' ),
|
||||
'hero_primary_label' => __( 'Ver projetos', 'gustavoo-portfolio-core' ),
|
||||
'hero_primary_url' => '#projetos',
|
||||
'hero_secondary_label' => __( 'Fale comigo', 'gustavoo-portfolio-core' ),
|
||||
'hero_secondary_url' => '#contato',
|
||||
'about_title' => __( 'Responsabilidade técnica do planejamento à produção.', 'gustavoo-portfolio-core' ),
|
||||
'about_text' => __( 'Mais do que escrever código, meu objetivo é garantir que seu projeto saia do papel e funcione exatamente como planejado. Assumo a responsabilidade técnica de ponta a ponta para que você foque no que importa: crescer.', 'gustavoo-portfolio-core' ),
|
||||
'about_secondary_text' => __( 'Já participei do desenvolvimento e lançamento de múltiplos projetos digitais, atuando da arquitetura técnica à divulgação e ao crescimento das plataformas. Essa combinação de visão técnica e entendimento de mercado orienta cada entrega.', 'gustavoo-portfolio-core' ),
|
||||
'contact_heading' => __( 'Tem um projeto em mente?', 'gustavoo-portfolio-core' ),
|
||||
'contact_text' => __( 'Conte o que você precisa construir ou melhorar. Responderei com os próximos passos para transformar a ideia em uma entrega concreta.', 'gustavoo-portfolio-core' ),
|
||||
'contact_form_id' => 0,
|
||||
'newsletter_heading' => __( 'Ideias úteis, direto na sua caixa de entrada.', 'gustavoo-portfolio-core' ),
|
||||
'newsletter_text' => __( 'Receba conteúdos sobre desenvolvimento, produto, infraestrutura e automação. Sem ruído e sem spam.', 'gustavoo-portfolio-core' ),
|
||||
'newsletter_form_id' => 0,
|
||||
'projects_title' => __( 'Projetos selecionados', 'gustavoo-portfolio-core' ),
|
||||
'projects_text' => __( 'Uma seleção de produtos, experiências e soluções que ajudei a colocar no mundo.', 'gustavoo-portfolio-core' ),
|
||||
'projects_limit' => 6,
|
||||
'blog_title' => __( 'Código, produto e bastidores', 'gustavoo-portfolio-core' ),
|
||||
'blog_text' => __( 'Análises práticas sobre desenvolvimento, infraestrutura, automação e crescimento de produtos digitais.', 'gustavoo-portfolio-core' ),
|
||||
'blog_limit' => 4,
|
||||
'email' => 'contato@gustavoo.me',
|
||||
'whatsapp' => '+55 31 99516-8069',
|
||||
'whatsapp_message' => __( 'Olá! Gostaria de falar sobre um projeto.', 'gustavoo-portfolio-core' ),
|
||||
'github_url' => 'https://github.com/gustavooth',
|
||||
'linkedin_url' => '',
|
||||
'instagram_url' => '',
|
||||
'availability_label' => __( 'Disponível para novos projetos', 'gustavoo-portfolio-core' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add missing defaults without overwriting existing values.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function ensure_defaults() {
|
||||
$existing = get_option( self::OPTION_NAME, null );
|
||||
|
||||
if ( null === $existing || ! is_array( $existing ) ) {
|
||||
add_option( self::OPTION_NAME, self::defaults(), '', false );
|
||||
return;
|
||||
}
|
||||
|
||||
$merged = array_merge( self::defaults(), $existing );
|
||||
if ( $merged !== $existing ) {
|
||||
update_option( self::OPTION_NAME, $merged, false );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return settings merged with defaults.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function get_settings() {
|
||||
$settings = get_option( self::OPTION_NAME, array() );
|
||||
|
||||
return array_merge( self::defaults(), is_array( $settings ) ? $settings : array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Settings submenu below Portfolio.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function add_settings_page() {
|
||||
add_submenu_page(
|
||||
'edit.php?post_type=' . GSO_Projects::POST_TYPE,
|
||||
__( 'Configurações do portfólio', 'gustavoo-portfolio-core' ),
|
||||
__( 'Configurações', 'gustavoo-portfolio-core' ),
|
||||
'manage_options',
|
||||
self::PAGE_SLUG,
|
||||
array( __CLASS__, 'render_settings_page' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the option, sections and fields.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_settings() {
|
||||
register_setting(
|
||||
self::OPTION_GROUP,
|
||||
self::OPTION_NAME,
|
||||
array(
|
||||
'type' => 'array',
|
||||
'description' => __( 'Configurações compartilhadas pelo tema Gustavo Portfolio.', 'gustavoo-portfolio-core' ),
|
||||
'sanitize_callback' => array( __CLASS__, 'sanitize_settings' ),
|
||||
'default' => self::defaults(),
|
||||
)
|
||||
);
|
||||
|
||||
$sections = self::sections();
|
||||
$fields = self::fields();
|
||||
|
||||
foreach ( $sections as $section_id => $section ) {
|
||||
add_settings_section(
|
||||
$section_id,
|
||||
$section['title'],
|
||||
array( __CLASS__, 'render_section_description' ),
|
||||
self::PAGE_SLUG,
|
||||
array( 'description' => $section['description'] )
|
||||
);
|
||||
}
|
||||
|
||||
foreach ( $fields as $key => $field ) {
|
||||
add_settings_field(
|
||||
'gso-setting-' . str_replace( '_', '-', $key ),
|
||||
$field['label'],
|
||||
array( __CLASS__, 'render_field' ),
|
||||
self::PAGE_SLUG,
|
||||
$field['section'],
|
||||
array_merge( $field, array( 'key' => $key ) )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings page section definitions.
|
||||
*
|
||||
* @return array<string, array<string, string>>
|
||||
*/
|
||||
private static function sections() {
|
||||
return array(
|
||||
'gso-settings-hero' => array(
|
||||
'title' => __( 'Hero', 'gustavoo-portfolio-core' ),
|
||||
'description' => __( 'Mensagem principal e chamadas para ação da página inicial.', 'gustavoo-portfolio-core' ),
|
||||
),
|
||||
'gso-settings-about' => array(
|
||||
'title' => __( 'Sobre', 'gustavoo-portfolio-core' ),
|
||||
'description' => __( 'Apresentação profissional exibida na seção Sobre.', 'gustavoo-portfolio-core' ),
|
||||
),
|
||||
'gso-settings-content' => array(
|
||||
'title' => __( 'Projetos e blog', 'gustavoo-portfolio-core' ),
|
||||
'description' => __( 'Títulos, textos de apoio e limites das listagens da página inicial.', 'gustavoo-portfolio-core' ),
|
||||
),
|
||||
'gso-settings-contact' => array(
|
||||
'title' => __( 'Contato', 'gustavoo-portfolio-core' ),
|
||||
'description' => __( 'Canais diretos e formulário principal do Fluent Forms.', 'gustavoo-portfolio-core' ),
|
||||
),
|
||||
'gso-settings-newsletter' => array(
|
||||
'title' => __( 'Newsletter', 'gustavoo-portfolio-core' ),
|
||||
'description' => __( 'Conteúdo e formulário usados pelo CTA e pelo widget de newsletter.', 'gustavoo-portfolio-core' ),
|
||||
),
|
||||
'gso-settings-social' => array(
|
||||
'title' => __( 'Redes sociais', 'gustavoo-portfolio-core' ),
|
||||
'description' => __( 'Perfis públicos exibidos no tema.', 'gustavoo-portfolio-core' ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings field schema.
|
||||
*
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
private static function fields() {
|
||||
return array(
|
||||
'hero_eyebrow' => self::field( 'gso-settings-hero', __( 'Sobretítulo', 'gustavoo-portfolio-core' ), 'text' ),
|
||||
'hero_title' => self::field( 'gso-settings-hero', __( 'Título', 'gustavoo-portfolio-core' ), 'text' ),
|
||||
'hero_description' => self::field( 'gso-settings-hero', __( 'Descrição', 'gustavoo-portfolio-core' ), 'textarea' ),
|
||||
'hero_primary_label' => self::field( 'gso-settings-hero', __( 'CTA principal — texto', 'gustavoo-portfolio-core' ), 'text' ),
|
||||
'hero_primary_url' => self::field( 'gso-settings-hero', __( 'CTA principal — destino', 'gustavoo-portfolio-core' ), 'link', __( 'Aceita URL HTTP(S), mailto:, tel:, caminho relativo ou âncora como #projetos.', 'gustavoo-portfolio-core' ) ),
|
||||
'hero_secondary_label' => self::field( 'gso-settings-hero', __( 'CTA secundário — texto', 'gustavoo-portfolio-core' ), 'text' ),
|
||||
'hero_secondary_url' => self::field( 'gso-settings-hero', __( 'CTA secundário — destino', 'gustavoo-portfolio-core' ), 'link', __( 'Aceita URL HTTP(S), mailto:, tel:, caminho relativo ou âncora.', 'gustavoo-portfolio-core' ) ),
|
||||
'about_title' => self::field( 'gso-settings-about', __( 'Título', 'gustavoo-portfolio-core' ), 'text' ),
|
||||
'about_text' => self::field( 'gso-settings-about', __( 'Texto', 'gustavoo-portfolio-core' ), 'textarea' ),
|
||||
'projects_title' => self::field( 'gso-settings-content', __( 'Projetos — título', 'gustavoo-portfolio-core' ), 'text' ),
|
||||
'projects_text' => self::field( 'gso-settings-content', __( 'Projetos — texto', 'gustavoo-portfolio-core' ), 'textarea' ),
|
||||
'projects_limit' => self::field( 'gso-settings-content', __( 'Projetos — quantidade', 'gustavoo-portfolio-core' ), 'number', __( 'Entre 1 e 24.', 'gustavoo-portfolio-core' ), array( 'min' => 1, 'max' => 24 ) ),
|
||||
'blog_title' => self::field( 'gso-settings-content', __( 'Blog — título', 'gustavoo-portfolio-core' ), 'text' ),
|
||||
'blog_text' => self::field( 'gso-settings-content', __( 'Blog — texto', 'gustavoo-portfolio-core' ), 'textarea' ),
|
||||
'blog_limit' => self::field( 'gso-settings-content', __( 'Blog — quantidade', 'gustavoo-portfolio-core' ), 'number', __( 'Entre 1 e 24.', 'gustavoo-portfolio-core' ), array( 'min' => 1, 'max' => 24 ) ),
|
||||
'contact_heading' => self::field( 'gso-settings-contact', __( 'Título', 'gustavoo-portfolio-core' ), 'text' ),
|
||||
'contact_text' => self::field( 'gso-settings-contact', __( 'Texto', 'gustavoo-portfolio-core' ), 'textarea' ),
|
||||
'contact_form_id' => self::field( 'gso-settings-contact', __( 'ID do formulário de contato', 'gustavoo-portfolio-core' ), 'number', __( 'ID numérico do Fluent Forms. O seed preenche este campo quando possível.', 'gustavoo-portfolio-core' ), array( 'min' => 0 ) ),
|
||||
'email' => self::field( 'gso-settings-contact', __( 'E-mail', 'gustavoo-portfolio-core' ), 'email' ),
|
||||
'whatsapp' => self::field( 'gso-settings-contact', __( 'WhatsApp', 'gustavoo-portfolio-core' ), 'tel', __( 'Inclua o DDI, por exemplo +55 31 99999-9999.', 'gustavoo-portfolio-core' ) ),
|
||||
'whatsapp_message' => self::field( 'gso-settings-contact', __( 'WhatsApp — mensagem inicial', 'gustavoo-portfolio-core' ), 'textarea', __( 'Texto que será preenchido automaticamente ao abrir o botão flutuante.', 'gustavoo-portfolio-core' ) ),
|
||||
'newsletter_heading' => self::field( 'gso-settings-newsletter', __( 'Título', 'gustavoo-portfolio-core' ), 'text' ),
|
||||
'newsletter_text' => self::field( 'gso-settings-newsletter', __( 'Texto', 'gustavoo-portfolio-core' ), 'textarea' ),
|
||||
'newsletter_form_id' => self::field( 'gso-settings-newsletter', __( 'ID do formulário de newsletter', 'gustavoo-portfolio-core' ), 'number', __( 'ID numérico do Fluent Forms. O seed preenche este campo quando possível.', 'gustavoo-portfolio-core' ), array( 'min' => 0 ) ),
|
||||
'github_url' => self::field( 'gso-settings-social', __( 'GitHub', 'gustavoo-portfolio-core' ), 'url' ),
|
||||
'linkedin_url' => self::field( 'gso-settings-social', __( 'LinkedIn', 'gustavoo-portfolio-core' ), 'url' ),
|
||||
'instagram_url' => self::field( 'gso-settings-social', __( 'Instagram', 'gustavoo-portfolio-core' ), 'url' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build one field definition.
|
||||
*
|
||||
* @param string $section Section ID.
|
||||
* @param string $label Label.
|
||||
* @param string $type Field type.
|
||||
* @param string $description Help text.
|
||||
* @param array<string, mixed> $extra Extra attributes.
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function field( $section, $label, $type, $description = '', $extra = array() ) {
|
||||
return array_merge(
|
||||
array(
|
||||
'section' => $section,
|
||||
'label' => $label,
|
||||
'type' => $type,
|
||||
'description' => $description,
|
||||
),
|
||||
$extra
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize every documented setting and retain unknown extension keys.
|
||||
*
|
||||
* @param mixed $input Raw submitted option.
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function sanitize_settings( $input ) {
|
||||
$current = get_option( self::OPTION_NAME, array() );
|
||||
$output = array_merge( self::defaults(), is_array( $current ) ? $current : array() );
|
||||
$input = is_array( $input ) ? $input : array();
|
||||
|
||||
foreach ( self::fields() as $key => $field ) {
|
||||
$value = $input[ $key ] ?? '';
|
||||
|
||||
switch ( $field['type'] ) {
|
||||
case 'textarea':
|
||||
$output[ $key ] = sanitize_textarea_field( $value );
|
||||
break;
|
||||
case 'email':
|
||||
$output[ $key ] = sanitize_email( $value );
|
||||
break;
|
||||
case 'tel':
|
||||
$output[ $key ] = self::sanitize_phone( $value );
|
||||
break;
|
||||
case 'url':
|
||||
$output[ $key ] = self::sanitize_public_url( $value );
|
||||
break;
|
||||
case 'link':
|
||||
$output[ $key ] = self::sanitize_link( $value );
|
||||
break;
|
||||
case 'number':
|
||||
$number = absint( $value );
|
||||
$min = isset( $field['min'] ) ? absint( $field['min'] ) : 0;
|
||||
$max = isset( $field['max'] ) ? absint( $field['max'] ) : PHP_INT_MAX;
|
||||
$output[ $key ] = min( $max, max( $min, $number ) );
|
||||
break;
|
||||
default:
|
||||
$output[ $key ] = sanitize_text_field( $value );
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a public social URL.
|
||||
*
|
||||
* @param mixed $value Raw value.
|
||||
* @return string
|
||||
*/
|
||||
private static function sanitize_public_url( $value ) {
|
||||
$url = esc_url_raw( trim( (string) $value ), array( 'http', 'https' ) );
|
||||
|
||||
return $url && wp_http_validate_url( $url ) ? $url : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a CTA link, including safe same-page anchors.
|
||||
*
|
||||
* @param mixed $value Raw value.
|
||||
* @return string
|
||||
*/
|
||||
private static function sanitize_link( $value ) {
|
||||
$value = trim( sanitize_text_field( $value ) );
|
||||
|
||||
if ( preg_match( '/^#[A-Za-z][A-Za-z0-9_:.\-]*$/', $value ) ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return esc_url_raw( $value, array( 'http', 'https', 'mailto', 'tel' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep common telephone punctuation and discard everything else.
|
||||
*
|
||||
* @param mixed $value Raw phone value.
|
||||
* @return string
|
||||
*/
|
||||
private static function sanitize_phone( $value ) {
|
||||
$value = sanitize_text_field( $value );
|
||||
$value = preg_replace( '/[^0-9+()\-\s]/', '', $value );
|
||||
|
||||
return is_string( $value ) ? trim( $value ) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a section introduction.
|
||||
*
|
||||
* @param array<string, mixed> $args Section arguments.
|
||||
* @return void
|
||||
*/
|
||||
public static function render_section_description( $args ) {
|
||||
if ( ! empty( $args['description'] ) ) {
|
||||
echo '<p>' . esc_html( $args['description'] ) . '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a settings field from its schema.
|
||||
*
|
||||
* @param array<string, mixed> $args Field arguments.
|
||||
* @return void
|
||||
*/
|
||||
public static function render_field( $args ) {
|
||||
$settings = self::get_settings();
|
||||
$key = $args['key'];
|
||||
$value = $settings[ $key ] ?? '';
|
||||
$id = 'gso-setting-' . str_replace( '_', '-', $key );
|
||||
$name = self::OPTION_NAME . '[' . $key . ']';
|
||||
|
||||
if ( 'textarea' === $args['type'] ) {
|
||||
printf(
|
||||
'<textarea class="large-text" id="%1$s" name="%2$s" rows="4">%3$s</textarea>',
|
||||
esc_attr( $id ),
|
||||
esc_attr( $name ),
|
||||
esc_textarea( (string) $value )
|
||||
);
|
||||
} else {
|
||||
$html_type = in_array( $args['type'], array( 'email', 'number', 'tel', 'url' ), true ) ? $args['type'] : 'text';
|
||||
$attrs = '';
|
||||
|
||||
if ( isset( $args['min'] ) ) {
|
||||
$attrs .= ' min="' . esc_attr( (string) $args['min'] ) . '"';
|
||||
}
|
||||
if ( isset( $args['max'] ) ) {
|
||||
$attrs .= ' max="' . esc_attr( (string) $args['max'] ) . '"';
|
||||
}
|
||||
|
||||
printf(
|
||||
'<input class="%1$s" id="%2$s" name="%3$s" type="%4$s" value="%5$s"%6$s>',
|
||||
'number' === $html_type ? 'small-text' : 'regular-text',
|
||||
esc_attr( $id ),
|
||||
esc_attr( $name ),
|
||||
esc_attr( $html_type ),
|
||||
esc_attr( (string) $value ),
|
||||
$attrs // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Attributes are assembled from escaped integers.
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! empty( $args['description'] ) ) {
|
||||
echo '<p class="description">' . esc_html( $args['description'] ) . '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the admin settings page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function render_settings_page() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php esc_html_e( 'Configurações do portfólio', 'gustavoo-portfolio-core' ); ?></h1>
|
||||
<p><?php esc_html_e( 'Estes valores são compartilhados com o tema e ficam preservados ao trocar a apresentação visual.', 'gustavoo-portfolio-core' ); ?></p>
|
||||
<?php self::render_seed_status(); ?>
|
||||
<form action="options.php" method="post">
|
||||
<?php
|
||||
settings_fields( self::OPTION_GROUP );
|
||||
do_settings_sections( self::PAGE_SLUG );
|
||||
submit_button();
|
||||
?>
|
||||
</form>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a concise status for optional form integrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function render_seed_status() {
|
||||
$state = GSO_Seeder::get_state();
|
||||
$fluent_forms = class_exists( '\\FluentForm\\App\\Models\\Form' );
|
||||
$fluent_crm = function_exists( 'FluentCrmApi' );
|
||||
$contact_id = absint( $state['contact_form_id'] ?? 0 );
|
||||
$newsletter_id = absint( $state['newsletter_form_id'] ?? 0 );
|
||||
$feeds_installed = ! empty( $state['crm_feeds'] );
|
||||
?>
|
||||
<div class="notice notice-info inline">
|
||||
<p><strong><?php esc_html_e( 'Integrações automáticas', 'gustavoo-portfolio-core' ); ?></strong></p>
|
||||
<ul>
|
||||
<li><?php echo esc_html( sprintf( 'Fluent Forms: %s', $fluent_forms ? __( 'ativo', 'gustavoo-portfolio-core' ) : __( 'inativo', 'gustavoo-portfolio-core' ) ) ); ?></li>
|
||||
<li><?php echo esc_html( sprintf( 'Formulário de contato: %s', $contact_id ? '#' . $contact_id : __( 'aguardando', 'gustavoo-portfolio-core' ) ) ); ?></li>
|
||||
<li><?php echo esc_html( sprintf( 'Formulário de newsletter: %s', $newsletter_id ? '#' . $newsletter_id : __( 'aguardando', 'gustavoo-portfolio-core' ) ) ); ?></li>
|
||||
<li><?php echo esc_html( sprintf( 'FluentCRM: %s', $fluent_crm ? ( $feeds_installed ? __( 'feeds configurados', 'gustavoo-portfolio-core' ) : __( 'ativo; configuração pendente', 'gustavoo-portfolio-core' ) ) : __( 'inativo', 'gustavoo-portfolio-core' ) ) ); ?></li>
|
||||
</ul>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a shortcut from the Plugins screen.
|
||||
*
|
||||
* @param array<int, string> $links Existing links.
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public static function plugin_action_links( $links ) {
|
||||
$url = admin_url( 'edit.php?post_type=' . GSO_Projects::POST_TYPE . '&page=' . self::PAGE_SLUG );
|
||||
|
||||
array_unshift(
|
||||
$links,
|
||||
'<a href="' . esc_url( $url ) . '">' . esc_html__( 'Configurações', 'gustavoo-portfolio-core' ) . '</a>'
|
||||
);
|
||||
|
||||
return $links;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,563 @@
|
||||
<?php
|
||||
/**
|
||||
* Project content model and admin UI.
|
||||
*
|
||||
* @package GustavoPortfolioCore
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Registers the portfolio project post type, taxonomies and metadata.
|
||||
*/
|
||||
final class GSO_Projects {
|
||||
const POST_TYPE = 'gso_project';
|
||||
const TAX_TYPE = 'gso_project_type';
|
||||
const TAX_TECH = 'gso_project_tech';
|
||||
|
||||
const META_URL = '_gso_project_url';
|
||||
const META_CLIENT = '_gso_project_client';
|
||||
const META_YEAR = '_gso_project_year';
|
||||
const META_FEATURED = '_gso_project_featured';
|
||||
const META_LINK_LABEL = '_gso_project_link_label';
|
||||
const META_ACCENT = '_gso_project_accent';
|
||||
|
||||
/**
|
||||
* Register hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'init', array( __CLASS__, 'register_content_types' ) );
|
||||
add_action( 'add_meta_boxes_' . self::POST_TYPE, array( __CLASS__, 'add_meta_boxes' ) );
|
||||
add_action( 'save_post_' . self::POST_TYPE, array( __CLASS__, 'save_meta_box' ) );
|
||||
|
||||
add_filter( 'manage_' . self::POST_TYPE . '_posts_columns', array( __CLASS__, 'admin_columns' ) );
|
||||
add_action( 'manage_' . self::POST_TYPE . '_posts_custom_column', array( __CLASS__, 'render_admin_column' ), 10, 2 );
|
||||
add_filter( 'manage_edit-' . self::POST_TYPE . '_sortable_columns', array( __CLASS__, 'sortable_columns' ) );
|
||||
add_action( 'pre_get_posts', array( __CLASS__, 'apply_admin_sorting' ) );
|
||||
add_action( 'restrict_manage_posts', array( __CLASS__, 'taxonomy_filters' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the post type, taxonomies and REST-aware metadata.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_content_types() {
|
||||
register_post_type(
|
||||
self::POST_TYPE,
|
||||
array(
|
||||
'labels' => array(
|
||||
'name' => __( 'Projetos', 'gustavoo-portfolio-core' ),
|
||||
'singular_name' => __( 'Projeto', 'gustavoo-portfolio-core' ),
|
||||
'menu_name' => __( 'Portfólio', 'gustavoo-portfolio-core' ),
|
||||
'name_admin_bar' => __( 'Projeto', 'gustavoo-portfolio-core' ),
|
||||
'add_new' => __( 'Adicionar projeto', 'gustavoo-portfolio-core' ),
|
||||
'add_new_item' => __( 'Adicionar novo projeto', 'gustavoo-portfolio-core' ),
|
||||
'edit_item' => __( 'Editar projeto', 'gustavoo-portfolio-core' ),
|
||||
'new_item' => __( 'Novo projeto', 'gustavoo-portfolio-core' ),
|
||||
'view_item' => __( 'Ver projeto', 'gustavoo-portfolio-core' ),
|
||||
'search_items' => __( 'Buscar projetos', 'gustavoo-portfolio-core' ),
|
||||
'not_found' => __( 'Nenhum projeto encontrado.', 'gustavoo-portfolio-core' ),
|
||||
'not_found_in_trash' => __( 'Nenhum projeto encontrado na lixeira.', 'gustavoo-portfolio-core' ),
|
||||
'all_items' => __( 'Projetos', 'gustavoo-portfolio-core' ),
|
||||
'featured_image' => __( 'Imagem do projeto', 'gustavoo-portfolio-core' ),
|
||||
'set_featured_image' => __( 'Definir imagem do projeto', 'gustavoo-portfolio-core' ),
|
||||
'remove_featured_image' => __( 'Remover imagem do projeto', 'gustavoo-portfolio-core' ),
|
||||
),
|
||||
'public' => false,
|
||||
'publicly_queryable' => false,
|
||||
'exclude_from_search' => true,
|
||||
'show_ui' => true,
|
||||
'show_in_menu' => true,
|
||||
'show_in_admin_bar' => true,
|
||||
'show_in_nav_menus' => false,
|
||||
'show_in_rest' => true,
|
||||
'menu_position' => 25,
|
||||
'menu_icon' => 'dashicons-portfolio',
|
||||
'capability_type' => 'post',
|
||||
'map_meta_cap' => true,
|
||||
'hierarchical' => false,
|
||||
'has_archive' => false,
|
||||
'rewrite' => false,
|
||||
'query_var' => false,
|
||||
'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'page-attributes', 'revisions', 'custom-fields' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_taxonomy(
|
||||
self::TAX_TYPE,
|
||||
self::POST_TYPE,
|
||||
array(
|
||||
'labels' => array(
|
||||
'name' => __( 'Tipos de projeto', 'gustavoo-portfolio-core' ),
|
||||
'singular_name' => __( 'Tipo de projeto', 'gustavoo-portfolio-core' ),
|
||||
'menu_name' => __( 'Tipos', 'gustavoo-portfolio-core' ),
|
||||
'all_items' => __( 'Todos os tipos', 'gustavoo-portfolio-core' ),
|
||||
'edit_item' => __( 'Editar tipo', 'gustavoo-portfolio-core' ),
|
||||
'add_new_item' => __( 'Adicionar tipo', 'gustavoo-portfolio-core' ),
|
||||
'search_items' => __( 'Buscar tipos', 'gustavoo-portfolio-core' ),
|
||||
),
|
||||
'public' => false,
|
||||
'publicly_queryable' => false,
|
||||
'show_ui' => true,
|
||||
'show_admin_column' => false,
|
||||
'show_in_rest' => true,
|
||||
'hierarchical' => true,
|
||||
'rewrite' => false,
|
||||
)
|
||||
);
|
||||
|
||||
register_taxonomy(
|
||||
self::TAX_TECH,
|
||||
self::POST_TYPE,
|
||||
array(
|
||||
'labels' => array(
|
||||
'name' => __( 'Tecnologias', 'gustavoo-portfolio-core' ),
|
||||
'singular_name' => __( 'Tecnologia', 'gustavoo-portfolio-core' ),
|
||||
'menu_name' => __( 'Tecnologias', 'gustavoo-portfolio-core' ),
|
||||
'all_items' => __( 'Todas as tecnologias', 'gustavoo-portfolio-core' ),
|
||||
'edit_item' => __( 'Editar tecnologia', 'gustavoo-portfolio-core' ),
|
||||
'add_new_item' => __( 'Adicionar tecnologia', 'gustavoo-portfolio-core' ),
|
||||
'search_items' => __( 'Buscar tecnologias', 'gustavoo-portfolio-core' ),
|
||||
'separate_items_with_commas' => __( 'Separe tecnologias com vírgulas', 'gustavoo-portfolio-core' ),
|
||||
),
|
||||
'public' => false,
|
||||
'publicly_queryable' => false,
|
||||
'show_ui' => true,
|
||||
'show_admin_column' => false,
|
||||
'show_in_rest' => true,
|
||||
'hierarchical' => false,
|
||||
'rewrite' => false,
|
||||
)
|
||||
);
|
||||
|
||||
self::register_meta();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register project metadata.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function register_meta() {
|
||||
$common = array(
|
||||
'single' => true,
|
||||
'show_in_rest' => true,
|
||||
'auth_callback' => array( __CLASS__, 'authorize_meta' ),
|
||||
);
|
||||
|
||||
register_post_meta(
|
||||
self::POST_TYPE,
|
||||
self::META_URL,
|
||||
array_merge(
|
||||
$common,
|
||||
array(
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( __CLASS__, 'sanitize_external_url' ),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( array( self::META_CLIENT, self::META_LINK_LABEL ) as $meta_key ) {
|
||||
register_post_meta(
|
||||
self::POST_TYPE,
|
||||
$meta_key,
|
||||
array_merge(
|
||||
$common,
|
||||
array(
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
register_post_meta(
|
||||
self::POST_TYPE,
|
||||
self::META_YEAR,
|
||||
array_merge(
|
||||
$common,
|
||||
array(
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => array( __CLASS__, 'sanitize_year' ),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
register_post_meta(
|
||||
self::POST_TYPE,
|
||||
self::META_FEATURED,
|
||||
array_merge(
|
||||
$common,
|
||||
array(
|
||||
'type' => 'boolean',
|
||||
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
register_post_meta(
|
||||
self::POST_TYPE,
|
||||
self::META_ACCENT,
|
||||
array_merge(
|
||||
$common,
|
||||
array(
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_hex_color',
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restrict meta updates to users who can edit the project.
|
||||
*
|
||||
* @param bool $allowed Current authorization result.
|
||||
* @param string $meta_key Meta key.
|
||||
* @param int $post_id Project ID.
|
||||
* @return bool
|
||||
*/
|
||||
public static function authorize_meta( $allowed, $meta_key, $post_id ) {
|
||||
unset( $allowed, $meta_key );
|
||||
|
||||
return current_user_can( 'edit_post', (int) $post_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow only valid external HTTP(S) URLs.
|
||||
*
|
||||
* @param mixed $value Raw URL.
|
||||
* @return string
|
||||
*/
|
||||
public static function sanitize_external_url( $value ) {
|
||||
$url = esc_url_raw( trim( (string) $value ), array( 'http', 'https' ) );
|
||||
|
||||
return $url && wp_http_validate_url( $url ) ? $url : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a four-digit project year.
|
||||
*
|
||||
* @param mixed $value Raw year.
|
||||
* @return int
|
||||
*/
|
||||
public static function sanitize_year( $value ) {
|
||||
$year = absint( $value );
|
||||
$max_year = (int) gmdate( 'Y' ) + 5;
|
||||
|
||||
return $year >= 1900 && $year <= $max_year ? $year : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the project details metabox.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function add_meta_boxes() {
|
||||
add_meta_box(
|
||||
'gso-project-details',
|
||||
__( 'Detalhes do projeto', 'gustavoo-portfolio-core' ),
|
||||
array( __CLASS__, 'render_meta_box' ),
|
||||
self::POST_TYPE,
|
||||
'normal',
|
||||
'high'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render project fields.
|
||||
*
|
||||
* @param WP_Post $post Current project.
|
||||
* @return void
|
||||
*/
|
||||
public static function render_meta_box( $post ) {
|
||||
$url = (string) get_post_meta( $post->ID, self::META_URL, true );
|
||||
$client = (string) get_post_meta( $post->ID, self::META_CLIENT, true );
|
||||
$year = absint( get_post_meta( $post->ID, self::META_YEAR, true ) );
|
||||
$featured = (bool) get_post_meta( $post->ID, self::META_FEATURED, true );
|
||||
$link_label = (string) get_post_meta( $post->ID, self::META_LINK_LABEL, true );
|
||||
$accent = sanitize_hex_color( get_post_meta( $post->ID, self::META_ACCENT, true ) );
|
||||
|
||||
if ( ! $accent ) {
|
||||
$accent = '#7cf3da';
|
||||
}
|
||||
|
||||
wp_nonce_field( 'gso_save_project_details', 'gso_project_details_nonce' );
|
||||
?>
|
||||
<table class="form-table" role="presentation">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row"><label for="gso-project-url"><?php esc_html_e( 'URL externa', 'gustavoo-portfolio-core' ); ?></label></th>
|
||||
<td>
|
||||
<input class="regular-text" id="gso-project-url" name="gso_project_url" type="url" value="<?php echo esc_attr( $url ); ?>" inputmode="url" placeholder="https://">
|
||||
<p class="description"><?php esc_html_e( 'Aceita somente uma URL completa usando HTTP ou HTTPS.', 'gustavoo-portfolio-core' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="gso-project-client"><?php esc_html_e( 'Cliente ou marca', 'gustavoo-portfolio-core' ); ?></label></th>
|
||||
<td><input class="regular-text" id="gso-project-client" name="gso_project_client" type="text" value="<?php echo esc_attr( $client ); ?>" maxlength="160"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="gso-project-year"><?php esc_html_e( 'Ano', 'gustavoo-portfolio-core' ); ?></label></th>
|
||||
<td><input class="small-text" id="gso-project-year" name="gso_project_year" type="number" value="<?php echo $year ? esc_attr( (string) $year ) : ''; ?>" min="1900" max="<?php echo esc_attr( (string) ( (int) gmdate( 'Y' ) + 5 ) ); ?>" step="1"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="gso-project-link-label"><?php esc_html_e( 'Texto do CTA', 'gustavoo-portfolio-core' ); ?></label></th>
|
||||
<td><input class="regular-text" id="gso-project-link-label" name="gso_project_link_label" type="text" value="<?php echo esc_attr( $link_label ); ?>" maxlength="80" placeholder="<?php esc_attr_e( 'Ver projeto', 'gustavoo-portfolio-core' ); ?>"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="gso-project-accent"><?php esc_html_e( 'Cor de acento', 'gustavoo-portfolio-core' ); ?></label></th>
|
||||
<td><input id="gso-project-accent" name="gso_project_accent" type="color" value="<?php echo esc_attr( $accent ); ?>"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><?php esc_html_e( 'Destaque', 'gustavoo-portfolio-core' ); ?></th>
|
||||
<td>
|
||||
<label for="gso-project-featured">
|
||||
<input id="gso-project-featured" name="gso_project_featured" type="checkbox" value="1" <?php checked( $featured ); ?>>
|
||||
<?php esc_html_e( 'Exibir entre os projetos em destaque', 'gustavoo-portfolio-core' ); ?>
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Save project fields after nonce and capability checks.
|
||||
*
|
||||
* @param int $post_id Project ID.
|
||||
* @return void
|
||||
*/
|
||||
public static function save_meta_box( $post_id ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified immediately below.
|
||||
$nonce = isset( $_POST['gso_project_details_nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['gso_project_details_nonce'] ) ) : '';
|
||||
|
||||
if ( ! $nonce || ! wp_verify_nonce( $nonce, 'gso_save_project_details' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || wp_is_post_revision( $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( self::POST_TYPE !== get_post_type( $post_id ) || ! current_user_can( 'edit_post', $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verified above.
|
||||
$url = isset( $_POST['gso_project_url'] ) ? self::sanitize_external_url( wp_unslash( $_POST['gso_project_url'] ) ) : '';
|
||||
$client = isset( $_POST['gso_project_client'] ) ? sanitize_text_field( wp_unslash( $_POST['gso_project_client'] ) ) : '';
|
||||
$year = isset( $_POST['gso_project_year'] ) ? self::sanitize_year( wp_unslash( $_POST['gso_project_year'] ) ) : 0;
|
||||
$link_label = isset( $_POST['gso_project_link_label'] ) ? sanitize_text_field( wp_unslash( $_POST['gso_project_link_label'] ) ) : '';
|
||||
$accent = isset( $_POST['gso_project_accent'] ) ? sanitize_hex_color( wp_unslash( $_POST['gso_project_accent'] ) ) : '';
|
||||
$featured = isset( $_POST['gso_project_featured'] ) ? '1' : '';
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing
|
||||
|
||||
self::update_or_delete_meta( $post_id, self::META_URL, $url );
|
||||
self::update_or_delete_meta( $post_id, self::META_CLIENT, $client );
|
||||
self::update_or_delete_meta( $post_id, self::META_YEAR, $year );
|
||||
self::update_or_delete_meta( $post_id, self::META_LINK_LABEL, $link_label );
|
||||
self::update_or_delete_meta( $post_id, self::META_ACCENT, $accent );
|
||||
self::update_or_delete_meta( $post_id, self::META_FEATURED, $featured );
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a non-empty value, otherwise remove the metadata row.
|
||||
*
|
||||
* @param int $post_id Project ID.
|
||||
* @param string $meta_key Metadata key.
|
||||
* @param mixed $value Sanitized value.
|
||||
* @return void
|
||||
*/
|
||||
private static function update_or_delete_meta( $post_id, $meta_key, $value ) {
|
||||
if ( '' === $value || 0 === $value || null === $value ) {
|
||||
delete_post_meta( $post_id, $meta_key );
|
||||
return;
|
||||
}
|
||||
|
||||
update_post_meta( $post_id, $meta_key, $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Define project list columns.
|
||||
*
|
||||
* @param array<string, string> $columns Existing columns.
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function admin_columns( $columns ) {
|
||||
return array(
|
||||
'cb' => $columns['cb'] ?? '<input type="checkbox">',
|
||||
'gso_thumbnail' => __( 'Imagem', 'gustavoo-portfolio-core' ),
|
||||
'title' => __( 'Projeto', 'gustavoo-portfolio-core' ),
|
||||
'gso_client' => __( 'Cliente', 'gustavoo-portfolio-core' ),
|
||||
'gso_type' => __( 'Tipo', 'gustavoo-portfolio-core' ),
|
||||
'gso_tech' => __( 'Tecnologias', 'gustavoo-portfolio-core' ),
|
||||
'gso_year' => __( 'Ano', 'gustavoo-portfolio-core' ),
|
||||
'gso_featured' => __( 'Destaque', 'gustavoo-portfolio-core' ),
|
||||
'gso_url' => __( 'URL', 'gustavoo-portfolio-core' ),
|
||||
'gso_menu_order' => __( 'Ordem', 'gustavoo-portfolio-core' ),
|
||||
'date' => $columns['date'] ?? __( 'Data', 'gustavoo-portfolio-core' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render one project list cell.
|
||||
*
|
||||
* @param string $column Column ID.
|
||||
* @param int $post_id Project ID.
|
||||
* @return void
|
||||
*/
|
||||
public static function render_admin_column( $column, $post_id ) {
|
||||
switch ( $column ) {
|
||||
case 'gso_thumbnail':
|
||||
if ( has_post_thumbnail( $post_id ) ) {
|
||||
echo wp_kses_post( get_the_post_thumbnail( $post_id, array( 64, 48 ), array( 'style' => 'width:64px;height:48px;object-fit:cover;border-radius:4px;' ) ) );
|
||||
} else {
|
||||
echo '<span aria-hidden="true">—</span><span class="screen-reader-text">' . esc_html__( 'Sem imagem', 'gustavoo-portfolio-core' ) . '</span>';
|
||||
}
|
||||
break;
|
||||
case 'gso_client':
|
||||
echo esc_html( (string) get_post_meta( $post_id, self::META_CLIENT, true ) ?: '—' );
|
||||
break;
|
||||
case 'gso_type':
|
||||
self::render_terms_column( $post_id, self::TAX_TYPE );
|
||||
break;
|
||||
case 'gso_tech':
|
||||
self::render_terms_column( $post_id, self::TAX_TECH );
|
||||
break;
|
||||
case 'gso_year':
|
||||
$year = absint( get_post_meta( $post_id, self::META_YEAR, true ) );
|
||||
echo $year ? esc_html( (string) $year ) : '—';
|
||||
break;
|
||||
case 'gso_featured':
|
||||
if ( get_post_meta( $post_id, self::META_FEATURED, true ) ) {
|
||||
echo '<span class="dashicons dashicons-star-filled" aria-hidden="true"></span><span class="screen-reader-text">' . esc_html__( 'Sim', 'gustavoo-portfolio-core' ) . '</span>';
|
||||
} else {
|
||||
echo '<span aria-hidden="true">—</span><span class="screen-reader-text">' . esc_html__( 'Não', 'gustavoo-portfolio-core' ) . '</span>';
|
||||
}
|
||||
break;
|
||||
case 'gso_url':
|
||||
$url = self::sanitize_external_url( get_post_meta( $post_id, self::META_URL, true ) );
|
||||
if ( $url ) {
|
||||
$host = wp_parse_url( $url, PHP_URL_HOST );
|
||||
printf(
|
||||
'<a href="%1$s" target="_blank" rel="noopener noreferrer external">%2$s<span class="screen-reader-text"> %3$s</span></a>',
|
||||
esc_url( $url ),
|
||||
esc_html( $host ?: $url ),
|
||||
esc_html__( '(abre em nova aba)', 'gustavoo-portfolio-core' )
|
||||
);
|
||||
} else {
|
||||
echo '—';
|
||||
}
|
||||
break;
|
||||
case 'gso_menu_order':
|
||||
echo esc_html( (string) get_post_field( 'menu_order', $post_id ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render taxonomy terms in an admin column.
|
||||
*
|
||||
* @param int $post_id Project ID.
|
||||
* @param string $taxonomy Taxonomy name.
|
||||
* @return void
|
||||
*/
|
||||
private static function render_terms_column( $post_id, $taxonomy ) {
|
||||
$terms = get_the_terms( $post_id, $taxonomy );
|
||||
|
||||
if ( ! $terms || is_wp_error( $terms ) ) {
|
||||
echo '—';
|
||||
return;
|
||||
}
|
||||
|
||||
echo esc_html( implode( ', ', wp_list_pluck( $terms, 'name' ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Make selected columns sortable.
|
||||
*
|
||||
* @param array<string, string> $columns Existing sortable columns.
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function sortable_columns( $columns ) {
|
||||
$columns['gso_client'] = 'gso_client';
|
||||
$columns['gso_year'] = 'gso_year';
|
||||
$columns['gso_featured'] = 'gso_featured';
|
||||
$columns['gso_menu_order'] = 'menu_order';
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply safe sorting to the main project admin query.
|
||||
*
|
||||
* @param WP_Query $query Current query.
|
||||
* @return void
|
||||
*/
|
||||
public static function apply_admin_sorting( $query ) {
|
||||
if ( ! is_admin() || ! $query->is_main_query() || self::POST_TYPE !== $query->get( 'post_type' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ( $query->get( 'orderby' ) ) {
|
||||
case 'gso_client':
|
||||
$query->set( 'meta_key', self::META_CLIENT );
|
||||
$query->set( 'orderby', 'meta_value' );
|
||||
break;
|
||||
case 'gso_year':
|
||||
$query->set( 'meta_key', self::META_YEAR );
|
||||
$query->set( 'orderby', 'meta_value_num' );
|
||||
break;
|
||||
case 'gso_featured':
|
||||
$query->set( 'meta_key', self::META_FEATURED );
|
||||
$query->set( 'orderby', 'meta_value_num' );
|
||||
break;
|
||||
case 'menu_order':
|
||||
$query->set( 'orderby', 'menu_order title' );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add type and technology filters above the project list.
|
||||
*
|
||||
* @param string $post_type Current list post type.
|
||||
* @return void
|
||||
*/
|
||||
public static function taxonomy_filters( $post_type ) {
|
||||
if ( self::POST_TYPE !== $post_type ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( array( self::TAX_TYPE, self::TAX_TECH ) as $taxonomy ) {
|
||||
$tax_object = get_taxonomy( $taxonomy );
|
||||
if ( ! $tax_object ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only list filter.
|
||||
$selected = isset( $_GET[ $taxonomy ] ) ? sanitize_title( wp_unslash( $_GET[ $taxonomy ] ) ) : '';
|
||||
|
||||
wp_dropdown_categories(
|
||||
array(
|
||||
'show_option_all' => sprintf(
|
||||
/* translators: %s: taxonomy label. */
|
||||
__( 'Todos: %s', 'gustavoo-portfolio-core' ),
|
||||
$tax_object->labels->name
|
||||
),
|
||||
'taxonomy' => $taxonomy,
|
||||
'name' => $taxonomy,
|
||||
'orderby' => 'name',
|
||||
'selected' => $selected,
|
||||
'hide_empty' => false,
|
||||
'hierarchical' => $tax_object->hierarchical,
|
||||
'value_field' => 'slug',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,619 @@
|
||||
<?php
|
||||
/**
|
||||
* Idempotent starter content and Fluent Forms integration.
|
||||
*
|
||||
* @package GustavoPortfolioCore
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Creates useful starter content once, without overwriting later admin edits.
|
||||
*/
|
||||
final class GSO_Seeder {
|
||||
const VERSION = '1.6.0';
|
||||
const STATE_OPTION = 'gso_portfolio_seed_state';
|
||||
const LOCK_OPTION = 'gso_portfolio_seed_lock';
|
||||
const SITE_OPTION = 'gso_portfolio_site_state';
|
||||
const PROJECT_META = '_gso_seed_key';
|
||||
const FORM_META = '_gustavoo_seed_key';
|
||||
const FEED_META = '_gustavoo_seed_fluentcrm_feed_id';
|
||||
|
||||
/**
|
||||
* Register deferred integration setup.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'init', array( __CLASS__, 'maybe_seed' ), 20 );
|
||||
add_action( 'after_switch_theme', array( __CLASS__, 'maybe_seed' ), 20 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed content available during activation. Fluent integrations run on init.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function activate() {
|
||||
self::seed_site_configuration();
|
||||
self::seed_projects();
|
||||
|
||||
$state = self::get_state();
|
||||
$state['projects_seeded'] = true;
|
||||
$state['status'] = 'pending';
|
||||
update_option( self::STATE_OPTION, $state, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a stable status contract for the admin screen.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function get_state() {
|
||||
$state = get_option( self::STATE_OPTION, array() );
|
||||
|
||||
return wp_parse_args(
|
||||
is_array( $state ) ? $state : array(),
|
||||
array(
|
||||
'version' => '',
|
||||
'status' => 'pending',
|
||||
'projects_seeded' => false,
|
||||
'contact_form_id' => 0,
|
||||
'newsletter_form_id' => 0,
|
||||
'crm_feeds' => false,
|
||||
'message' => '',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete integration setup when Fluent Forms and FluentCRM are ready.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function maybe_seed() {
|
||||
self::seed_site_configuration();
|
||||
|
||||
$state = self::get_state();
|
||||
|
||||
if ( self::VERSION === $state['version'] && 'complete' === $state['status'] ) {
|
||||
self::maybe_migrate_contact_form();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! post_type_exists( GSO_Projects::POST_TYPE ) ) {
|
||||
GSO_Projects::register_content_types();
|
||||
}
|
||||
|
||||
self::seed_projects();
|
||||
|
||||
if ( ! self::fluent_is_ready() ) {
|
||||
$state['projects_seeded'] = true;
|
||||
$state['status'] = 'waiting';
|
||||
$state['message'] = __( 'Aguardando Fluent Forms e FluentCRM ativos.', 'gustavoo-portfolio-core' );
|
||||
update_option( self::STATE_OPTION, $state, false );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! add_option( self::LOCK_OPTION, time(), '', false ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$contact_id = self::upsert_form( 'contact', __( 'Site — Contato e orçamento', 'gustavoo-portfolio-core' ), true );
|
||||
$newsletter_id = self::upsert_form( 'newsletter', __( 'Site — Newsletter', 'gustavoo-portfolio-core' ), false );
|
||||
|
||||
self::upsert_crm_feed( $contact_id, 'contact' );
|
||||
self::upsert_crm_feed( $newsletter_id, 'newsletter' );
|
||||
self::save_form_ids( $contact_id, $newsletter_id );
|
||||
|
||||
update_option(
|
||||
self::STATE_OPTION,
|
||||
array(
|
||||
'version' => self::VERSION,
|
||||
'status' => 'complete',
|
||||
'projects_seeded' => true,
|
||||
'contact_form_id' => $contact_id,
|
||||
'newsletter_form_id' => $newsletter_id,
|
||||
'crm_feeds' => true,
|
||||
'message' => __( 'Formulários e feeds do FluentCRM configurados.', 'gustavoo-portfolio-core' ),
|
||||
),
|
||||
false
|
||||
);
|
||||
} catch ( Throwable $error ) {
|
||||
$state['status'] = 'error';
|
||||
$state['message'] = sanitize_text_field( $error->getMessage() );
|
||||
update_option( self::STATE_OPTION, $state, false );
|
||||
} finally {
|
||||
delete_option( self::LOCK_OPTION );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the WordPress pieces that make the bundled theme work on a
|
||||
* fresh installation.
|
||||
*
|
||||
* Existing sites are left untouched: each value is written only when the
|
||||
* corresponding WordPress setting still has its fresh-install value.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function seed_site_configuration() {
|
||||
$state = get_option( self::SITE_OPTION, array() );
|
||||
|
||||
if ( ! is_array( $state ) || empty( $state['pages'] ) ) {
|
||||
$home_id = self::ensure_seed_page( 'inicio', __( 'Início', 'gustavoo-portfolio-core' ) );
|
||||
$blog_id = self::ensure_seed_page( 'blog', __( 'Blog', 'gustavoo-portfolio-core' ) );
|
||||
|
||||
if ( $home_id && $blog_id && 'posts' === get_option( 'show_on_front' ) ) {
|
||||
update_option( 'show_on_front', 'page' );
|
||||
update_option( 'page_on_front', $home_id );
|
||||
update_option( 'page_for_posts', $blog_id );
|
||||
}
|
||||
|
||||
if ( $home_id && $blog_id ) {
|
||||
$state = is_array( $state ) ? $state : array();
|
||||
$state['pages'] = true;
|
||||
update_option( self::SITE_OPTION, $state, false );
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $state['widgets'] ) ) {
|
||||
self::seed_theme_widgets();
|
||||
$state = is_array( $state ) ? $state : array();
|
||||
$state['widgets'] = true;
|
||||
update_option( self::SITE_OPTION, $state, false );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a required empty page once and identify it by a private marker.
|
||||
*
|
||||
* @param string $slug Page slug.
|
||||
* @param string $title Page title.
|
||||
* @return int
|
||||
*/
|
||||
private static function ensure_seed_page( $slug, $title ) {
|
||||
$existing = get_posts(
|
||||
array(
|
||||
'post_type' => 'page',
|
||||
'post_status' => 'any',
|
||||
'posts_per_page' => 1,
|
||||
'fields' => 'ids',
|
||||
'meta_key' => '_gso_seed_page',
|
||||
'meta_value' => $slug,
|
||||
)
|
||||
);
|
||||
|
||||
if ( $existing ) {
|
||||
return absint( $existing[0] );
|
||||
}
|
||||
|
||||
$page = get_page_by_path( $slug, OBJECT, 'page' );
|
||||
if ( $page ) {
|
||||
update_post_meta( $page->ID, '_gso_seed_page', $slug );
|
||||
return absint( $page->ID );
|
||||
}
|
||||
|
||||
$page_id = wp_insert_post(
|
||||
array(
|
||||
'post_type' => 'page',
|
||||
'post_status' => 'publish',
|
||||
'post_title' => $title,
|
||||
'post_name' => $slug,
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
if ( is_wp_error( $page_id ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
update_post_meta( $page_id, '_gso_seed_page', $slug );
|
||||
return absint( $page_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reproduce the sidebar and footer widget arrangement from this site.
|
||||
* Widgets are added only to empty areas, so a user's arrangement wins.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function seed_theme_widgets() {
|
||||
$sidebars = wp_get_sidebars_widgets();
|
||||
$sidebars = is_array( $sidebars ) ? $sidebars : array();
|
||||
|
||||
$blog_widgets = isset( $sidebars['sidebar-blog'] ) ? (array) $sidebars['sidebar-blog'] : array();
|
||||
$can_seed_blog = empty( $blog_widgets ) || array( 'gustavoo_portfolio_newsletter-1' ) === $blog_widgets;
|
||||
|
||||
if ( $can_seed_blog ) {
|
||||
$newsletter = get_option( 'widget_gustavoo_portfolio_newsletter', array() );
|
||||
$newsletter = is_array( $newsletter ) ? $newsletter : array();
|
||||
$newsletter[1] = array(
|
||||
'title' => __( 'Newsletter', 'gustavoo-portfolio-core' ),
|
||||
'text' => __( 'Receba novos artigos sobre desenvolvimento, infraestrutura e automação.', 'gustavoo-portfolio-core' ),
|
||||
'form_id' => 0,
|
||||
);
|
||||
$newsletter['_multiwidget'] = 1;
|
||||
update_option( 'widget_gustavoo_portfolio_newsletter', $newsletter, false );
|
||||
|
||||
$blocks = get_option( 'widget_block', array() );
|
||||
$blocks = is_array( $blocks ) ? $blocks : array();
|
||||
$blocks[2] = array( 'content' => '<!-- wp:search /-->' );
|
||||
$blocks[3] = array( 'content' => '<!-- wp:group --><div class="wp-block-group"><!-- wp:heading --><h2>Posts recentes</h2><!-- /wp:heading --><!-- wp:latest-posts /--></div><!-- /wp:group -->' );
|
||||
$blocks[4] = array( 'content' => '<!-- wp:group --><div class="wp-block-group"><!-- wp:heading --><h2>Comentários</h2><!-- /wp:heading --><!-- wp:latest-comments {"displayAvatar":false,"displayDate":false,"displayExcerpt":false} /--></div><!-- /wp:group -->' );
|
||||
$blocks[5] = array( 'content' => '<!-- wp:group --><div class="wp-block-group"><!-- wp:heading --><h2>Arquivos</h2><!-- /wp:heading --><!-- wp:archives /--></div><!-- /wp:group -->' );
|
||||
$blocks[6] = array( 'content' => '<!-- wp:group --><div class="wp-block-group"><!-- wp:heading --><h2>Categorias</h2><!-- /wp:heading --><!-- wp:categories /--></div><!-- /wp:group -->' );
|
||||
$blocks['_multiwidget'] = 1;
|
||||
update_option( 'widget_block', $blocks, false );
|
||||
|
||||
$sidebars['sidebar-blog'] = array( 'gustavoo_portfolio_newsletter-1', 'block-2', 'block-3', 'block-4' );
|
||||
}
|
||||
|
||||
if ( empty( $sidebars['footer-1'] ) ) {
|
||||
$sidebars['footer-1'] = array( 'block-5', 'block-6' );
|
||||
}
|
||||
|
||||
update_option( 'sidebars_widgets', $sidebars, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Repair the seeded contact form when an earlier migration did not persist.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function maybe_migrate_contact_form() {
|
||||
if ( ! self::fluent_is_ready() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$form = self::find_seeded_form( 'contact' );
|
||||
|
||||
if ( ! $form ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = json_decode( (string) $form->form_fields, true );
|
||||
$has_message = false;
|
||||
|
||||
foreach ( (array) ( $fields['fields'] ?? array() ) as $field ) {
|
||||
if ( 'message' === (string) ( $field['attributes']['name'] ?? '' ) && 'textarea' === (string) ( $field['element'] ?? '' ) ) {
|
||||
$has_message = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $has_message ) {
|
||||
try {
|
||||
self::upsert_form( 'contact', __( 'Site — Contato e orçamento', 'gustavoo-portfolio-core' ), true );
|
||||
} catch ( Throwable $error ) {
|
||||
// Keep the front end available if Fluent Forms is temporarily unavailable.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the optional integrations without triggering autoload errors.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function fluent_is_ready() {
|
||||
return defined( 'FLUENTFORM' )
|
||||
&& defined( 'FLUENTCRM' )
|
||||
&& function_exists( 'fluentformLoadFile' )
|
||||
&& function_exists( 'FluentCrmApi' )
|
||||
&& class_exists( '\\FluentForm\\App\\Models\\Form' )
|
||||
&& class_exists( '\\FluentForm\\App\\Models\\FormMeta' )
|
||||
&& class_exists( '\\FluentForm\\App\\Services\\Form\\FormService' )
|
||||
&& class_exists( '\\FluentForm\\App\\Services\\Integrations\\FormIntegrationService' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or reuse a seed-owned Fluent Form.
|
||||
*
|
||||
* @param string $key Deterministic form key.
|
||||
* @param string $title Form title.
|
||||
* @param bool $include_whatsapp Include the contact message field.
|
||||
* @return int
|
||||
* @throws Exception When Fluent Forms cannot create the form.
|
||||
*/
|
||||
private static function upsert_form( $key, $title, $include_whatsapp ) {
|
||||
$form = self::find_seeded_form( $key );
|
||||
|
||||
$defaults = fluentformLoadFile( 'Services/FormBuilder/DefaultElements.php' );
|
||||
$blank = \FluentForm\App\Models\Form::resolvePredefinedForm(
|
||||
array(
|
||||
'predefined' => 'blank_form',
|
||||
'type' => 'form',
|
||||
)
|
||||
);
|
||||
$structure = json_decode( (string) $blank['form_fields'], true );
|
||||
|
||||
if ( ! is_array( $defaults ) || ! is_array( $structure ) ) {
|
||||
throw new Exception( 'Não foi possível carregar a estrutura padrão do Fluent Forms.' );
|
||||
}
|
||||
|
||||
$email = $defaults['general']['input_email'];
|
||||
$email['uniqElKey'] = 'el_gso_' . $key . '_email';
|
||||
$email['attributes']['name'] = 'email';
|
||||
$email['attributes']['placeholder'] = __( 'Seu melhor e-mail', 'gustavoo-portfolio-core' );
|
||||
$email['settings']['label'] = __( 'E-mail', 'gustavoo-portfolio-core' );
|
||||
$email['settings']['admin_field_label'] = __( 'E-mail', 'gustavoo-portfolio-core' );
|
||||
$email['settings']['validation_rules']['required']['value'] = true;
|
||||
|
||||
$fields = array( $email );
|
||||
|
||||
if ( $include_whatsapp ) {
|
||||
$whatsapp = $defaults['general']['input_mask'];
|
||||
$whatsapp['uniqElKey'] = 'el_gso_' . $key . '_whatsapp';
|
||||
$whatsapp['attributes']['name'] = 'whatsapp';
|
||||
$whatsapp['attributes']['placeholder'] = '(31) 99999-9999';
|
||||
$whatsapp['attributes']['data-mask'] = '(00) 00000-0000';
|
||||
$whatsapp['settings']['label'] = __( 'WhatsApp', 'gustavoo-portfolio-core' );
|
||||
$whatsapp['settings']['admin_field_label'] = __( 'WhatsApp', 'gustavoo-portfolio-core' );
|
||||
$whatsapp['settings']['mobile_keyboard_type'] = 'tel';
|
||||
$whatsapp['settings']['temp_mask'] = 'custom';
|
||||
$whatsapp['settings']['data-mask-reverse'] = 'no';
|
||||
$whatsapp['settings']['validation_rules']['required']['value'] = true;
|
||||
$fields[] = $whatsapp;
|
||||
|
||||
$message = $defaults['general']['textarea'];
|
||||
$message['uniqElKey'] = 'el_gso_' . $key . '_message';
|
||||
$message['attributes']['name'] = 'message';
|
||||
$message['attributes']['placeholder'] = __( 'Descreva o motivo do contato e o que você quer construir.', 'gustavoo-portfolio-core' );
|
||||
$message['attributes']['rows'] = 5;
|
||||
$message['settings']['label'] = __( 'Como posso ajudar?', 'gustavoo-portfolio-core' );
|
||||
$message['settings']['admin_field_label'] = __( 'Motivo do contato', 'gustavoo-portfolio-core' );
|
||||
$message['settings']['validation_rules']['required']['value'] = true;
|
||||
$fields[] = $message;
|
||||
}
|
||||
|
||||
$structure['fields'] = $fields;
|
||||
$structure['submitButton']['settings']['button_ui']['text'] = $include_whatsapp
|
||||
? __( 'Solicitar contato', 'gustavoo-portfolio-core' )
|
||||
: __( 'Quero receber', 'gustavoo-portfolio-core' );
|
||||
|
||||
$service = new \FluentForm\App\Services\Form\FormService();
|
||||
if ( $form ) {
|
||||
$service->update(
|
||||
array(
|
||||
'form_id' => $form->id,
|
||||
'title' => $title,
|
||||
'status' => 'published',
|
||||
'formFields' => wp_json_encode( $structure, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ),
|
||||
)
|
||||
);
|
||||
self::localize_form_confirmation( $form->id, $include_whatsapp );
|
||||
return absint( $form->id );
|
||||
}
|
||||
|
||||
$form = $service->store(
|
||||
array(
|
||||
'predefined' => 'blank_form',
|
||||
'type' => 'form',
|
||||
)
|
||||
);
|
||||
$form = $service->update(
|
||||
array(
|
||||
'form_id' => $form->id,
|
||||
'title' => $title,
|
||||
'status' => 'published',
|
||||
'formFields' => wp_json_encode( $structure, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ),
|
||||
)
|
||||
);
|
||||
|
||||
\FluentForm\App\Models\FormMeta::persist( $form->id, self::FORM_META, $key );
|
||||
self::localize_form_confirmation( $form->id, $include_whatsapp );
|
||||
|
||||
return absint( $form->id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate a form owned by this seed.
|
||||
*
|
||||
* @param string $key Seed key.
|
||||
* @return object|null
|
||||
*/
|
||||
private static function find_seeded_form( $key ) {
|
||||
$markers = \FluentForm\App\Models\FormMeta::where( 'meta_key', self::FORM_META )
|
||||
->where( 'value', $key )
|
||||
->get();
|
||||
|
||||
foreach ( $markers as $marker ) {
|
||||
$form = \FluentForm\App\Models\Form::find( absint( $marker->form_id ) );
|
||||
if ( $form ) {
|
||||
return $form;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a concise Portuguese success message.
|
||||
*
|
||||
* @param int $form_id Form ID.
|
||||
* @param bool $is_contact_form Whether this is the lead form.
|
||||
* @return void
|
||||
*/
|
||||
private static function localize_form_confirmation( $form_id, $is_contact_form ) {
|
||||
$settings = \FluentForm\App\Models\FormMeta::retrieve( 'formSettings', $form_id, array() );
|
||||
$settings = is_array( $settings ) ? $settings : array();
|
||||
|
||||
$settings['confirmation'] = wp_parse_args(
|
||||
array(
|
||||
'redirectTo' => 'samePage',
|
||||
'messageToShow' => $is_contact_form
|
||||
? __( 'Recebi seus dados. Entrarei em contato em breve.', 'gustavoo-portfolio-core' )
|
||||
: __( 'Inscrição recebida. Confira seu e-mail para confirmar.', 'gustavoo-portfolio-core' ),
|
||||
'samePageFormBehavior' => 'hide_form',
|
||||
),
|
||||
isset( $settings['confirmation'] ) && is_array( $settings['confirmation'] ) ? $settings['confirmation'] : array()
|
||||
);
|
||||
|
||||
\FluentForm\App\Models\FormMeta::persist( $form_id, 'formSettings', $settings );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the FluentCRM feed for one seed-owned form.
|
||||
*
|
||||
* @param int $form_id Form ID.
|
||||
* @param string $context Contact or newsletter.
|
||||
* @return void
|
||||
*/
|
||||
private static function upsert_crm_feed( $form_id, $context ) {
|
||||
$existing_id = absint( \FluentForm\App\Models\FormMeta::retrieve( self::FEED_META, $form_id, 0 ) );
|
||||
$defaults = apply_filters( 'fluentform/get_integration_defaults_fluentcrm', array(), $form_id );
|
||||
$defaults = is_array( $defaults ) ? $defaults : array();
|
||||
$is_contact = 'contact' === $context;
|
||||
$feed = array_replace(
|
||||
$defaults,
|
||||
array(
|
||||
'name' => $is_contact ? 'Site / Contato' : 'Site / Newsletter',
|
||||
'email' => 'email',
|
||||
'enabled' => true,
|
||||
'double_opt_in' => ! $is_contact,
|
||||
'other_fields' => $is_contact
|
||||
? array(
|
||||
array(
|
||||
'label' => 'phone',
|
||||
'item_value' => '{inputs.whatsapp}',
|
||||
),
|
||||
)
|
||||
: array(),
|
||||
'conditionals' => array(
|
||||
'status' => false,
|
||||
'type' => 'all',
|
||||
'conditions' => array(),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$result = ( new \FluentForm\App\Services\Integrations\FormIntegrationService() )->update(
|
||||
array(
|
||||
'form_id' => $form_id,
|
||||
'integration_id' => $existing_id,
|
||||
'integration_name' => 'fluentcrm',
|
||||
'data_type' => 'array',
|
||||
'status' => true,
|
||||
'integration' => $feed,
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! empty( $result['integration_id'] ) ) {
|
||||
\FluentForm\App\Models\FormMeta::persist( $form_id, self::FEED_META, absint( $result['integration_id'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Share form IDs with the theme settings option.
|
||||
*
|
||||
* @param int $contact_id Contact form ID.
|
||||
* @param int $newsletter_id Newsletter form ID.
|
||||
* @return void
|
||||
*/
|
||||
private static function save_form_ids( $contact_id, $newsletter_id ) {
|
||||
$settings = GSO_Portfolio_Settings::get_settings();
|
||||
$settings['contact_form_id'] = absint( $contact_id );
|
||||
$settings['newsletter_form_id'] = absint( $newsletter_id );
|
||||
update_option( GSO_Portfolio_Settings::OPTION_NAME, $settings, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add starter projects only when their deterministic markers are absent.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function seed_projects() {
|
||||
$projects = array(
|
||||
'aurelia-online' => array(
|
||||
'title' => 'Aurelia Online',
|
||||
'excerpt' => __( 'Plataforma online desenvolvida com foco em performance, experiência do usuário e evolução contínua.', 'gustavoo-portfolio-core' ),
|
||||
'type' => 'Plataforma web',
|
||||
'technologies' => array( 'Web', 'APIs', 'Infraestrutura' ),
|
||||
'featured' => true,
|
||||
),
|
||||
'lumenix-engine' => array(
|
||||
'title' => 'Lumenix Engine',
|
||||
'excerpt' => __( 'Engine e ecossistema técnico para experiências interativas e produtos digitais.', 'gustavoo-portfolio-core' ),
|
||||
'type' => 'Games',
|
||||
'technologies' => array( 'C++', 'Games', 'Performance' ),
|
||||
'featured' => true,
|
||||
),
|
||||
'aurabet' => array(
|
||||
'title' => 'AuraBet',
|
||||
'excerpt' => __( 'Produto digital com arquitetura, integrações e infraestrutura preparadas para escala.', 'gustavoo-portfolio-core' ),
|
||||
'type' => 'Produto digital',
|
||||
'technologies' => array( 'Full Stack', 'Docker', 'APIs' ),
|
||||
'featured' => true,
|
||||
),
|
||||
'visionforge' => array(
|
||||
'title' => 'VisionForge',
|
||||
'excerpt' => __( 'Solução criada para transformar processos complexos em uma experiência clara e eficiente.', 'gustavoo-portfolio-core' ),
|
||||
'type' => 'Software',
|
||||
'technologies' => array( 'Automação', 'Dados', 'Web' ),
|
||||
'featured' => true,
|
||||
),
|
||||
'papel-e-cor' => array(
|
||||
'title' => 'Papel & Cor',
|
||||
'excerpt' => __( 'Experiência de e-commerce otimizada para catálogo, compra e conversão.', 'gustavoo-portfolio-core' ),
|
||||
'type' => 'E-commerce',
|
||||
'technologies' => array( 'WordPress', 'WooCommerce', 'UX' ),
|
||||
'featured' => false,
|
||||
),
|
||||
'lumina-hub' => array(
|
||||
'title' => 'Lumina Hub',
|
||||
'excerpt' => __( 'Hub digital que reúne conteúdo, serviços e integrações em uma única plataforma.', 'gustavoo-portfolio-core' ),
|
||||
'type' => 'Plataforma web',
|
||||
'technologies' => array( 'WordPress', 'APIs', 'Automação' ),
|
||||
'featured' => false,
|
||||
),
|
||||
);
|
||||
|
||||
$order = 0;
|
||||
foreach ( $projects as $key => $project ) {
|
||||
$existing = get_posts(
|
||||
array(
|
||||
'post_type' => GSO_Projects::POST_TYPE,
|
||||
'post_status' => 'any',
|
||||
'posts_per_page' => 1,
|
||||
'fields' => 'ids',
|
||||
'meta_key' => self::PROJECT_META,
|
||||
'meta_value' => $key,
|
||||
)
|
||||
);
|
||||
|
||||
if ( $existing ) {
|
||||
++$order;
|
||||
continue;
|
||||
}
|
||||
|
||||
$post_id = wp_insert_post(
|
||||
array(
|
||||
'post_type' => GSO_Projects::POST_TYPE,
|
||||
'post_status' => 'publish',
|
||||
'post_title' => $project['title'],
|
||||
'post_excerpt' => $project['excerpt'],
|
||||
'menu_order' => $order,
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
if ( is_wp_error( $post_id ) ) {
|
||||
++$order;
|
||||
continue;
|
||||
}
|
||||
|
||||
update_post_meta( $post_id, self::PROJECT_META, $key );
|
||||
update_post_meta( $post_id, GSO_Projects::META_URL, 'https://gustavoo.me/#projetos' );
|
||||
update_post_meta( $post_id, GSO_Projects::META_CLIENT, __( 'Projeto selecionado', 'gustavoo-portfolio-core' ) );
|
||||
update_post_meta( $post_id, GSO_Projects::META_YEAR, (int) gmdate( 'Y' ) );
|
||||
update_post_meta( $post_id, GSO_Projects::META_LINK_LABEL, __( 'Conhecer projeto', 'gustavoo-portfolio-core' ) );
|
||||
update_post_meta( $post_id, GSO_Projects::META_FEATURED, $project['featured'] ? '1' : '' );
|
||||
wp_set_object_terms( $post_id, $project['type'], GSO_Projects::TAX_TYPE );
|
||||
wp_set_object_terms( $post_id, $project['technologies'], GSO_Projects::TAX_TECH );
|
||||
++$order;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* Not found template.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
get_header();
|
||||
|
||||
$gustavoo_recent_posts = get_posts(
|
||||
array(
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => 3,
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => true,
|
||||
)
|
||||
);
|
||||
?>
|
||||
|
||||
<main id="primary" class="site-main error-404">
|
||||
<section class="page-hero page-hero--error" aria-labelledby="error-title">
|
||||
<div class="site-shell page-hero__inner">
|
||||
<p class="eyebrow">404</p>
|
||||
<h1 id="error-title" class="page-hero__title"><?php esc_html_e( 'Esta página saiu do mapa.', 'gustavoo-portfolio' ); ?></h1>
|
||||
<p class="page-hero__description"><?php esc_html_e( 'O endereço pode ter mudado ou não existir mais. Pesquise pelo conteúdo ou retorne ao início.', 'gustavoo-portfolio' ); ?></p>
|
||||
<div class="page-hero__actions">
|
||||
<a class="button button--primary" href="<?php echo esc_url( home_url( '/' ) ); ?>"><?php esc_html_e( 'Voltar ao início', 'gustavoo-portfolio' ); ?> <span aria-hidden="true">→</span></a>
|
||||
</div>
|
||||
<div class="page-hero__search"><?php get_search_form(); ?></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php if ( $gustavoo_recent_posts ) : ?>
|
||||
<section class="section section--error-posts" aria-labelledby="error-posts-title">
|
||||
<div class="site-shell">
|
||||
<header class="section__header">
|
||||
<p class="eyebrow"><?php esc_html_e( 'Continue explorando', 'gustavoo-portfolio' ); ?></p>
|
||||
<h2 id="error-posts-title" class="section__title"><?php esc_html_e( 'Artigos recentes', 'gustavoo-portfolio' ); ?></h2>
|
||||
</header>
|
||||
<div class="posts-grid">
|
||||
<?php
|
||||
global $post;
|
||||
foreach ( $gustavoo_recent_posts as $post ) {
|
||||
setup_postdata( $post );
|
||||
get_template_part( 'template-parts/global/post-card' );
|
||||
}
|
||||
wp_reset_postdata();
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
|
||||
<?php
|
||||
get_footer();
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* Category, tag, author, and date archives.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
get_header();
|
||||
?>
|
||||
|
||||
<main id="primary" class="site-main archive-main">
|
||||
<header class="page-hero page-hero--archive">
|
||||
<div class="site-shell page-hero__inner">
|
||||
<p class="eyebrow"><?php esc_html_e( 'Arquivo', 'gustavoo-portfolio' ); ?></p>
|
||||
<h1 class="page-hero__title"><?php echo esc_html( wp_strip_all_tags( get_the_archive_title() ) ); ?></h1>
|
||||
<?php if ( get_the_archive_description() ) : ?>
|
||||
<div class="page-hero__description prose"><?php echo wp_kses_post( get_the_archive_description() ); ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="site-shell article-layout">
|
||||
<div class="article-layout__main">
|
||||
<?php if ( have_posts() ) : ?>
|
||||
<div class="posts-grid">
|
||||
<?php
|
||||
while ( have_posts() ) {
|
||||
the_post();
|
||||
get_template_part( 'template-parts/global/post-card' );
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php gustavoo_portfolio_pagination(); ?>
|
||||
<?php else : ?>
|
||||
<?php get_template_part( 'template-parts/content/content', 'none' ); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php get_sidebar(); ?>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php
|
||||
get_footer();
|
||||
@@ -0,0 +1,57 @@
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url("../fonts/inter-variable.woff2") format("woff2-variations");
|
||||
}
|
||||
|
||||
.editor-styles-wrapper {
|
||||
color: #d4dde3;
|
||||
background: #071624;
|
||||
font-family: "Inter", system-ui, sans-serif;
|
||||
font-size: 17px;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.editor-styles-wrapper .wp-block {
|
||||
max-width: 760px;
|
||||
}
|
||||
|
||||
.editor-styles-wrapper .wp-block[data-align="wide"] {
|
||||
max-width: 1160px;
|
||||
}
|
||||
|
||||
.editor-styles-wrapper h1,
|
||||
.editor-styles-wrapper h2,
|
||||
.editor-styles-wrapper h3,
|
||||
.editor-styles-wrapper h4,
|
||||
.editor-styles-wrapper h5,
|
||||
.editor-styles-wrapper h6 {
|
||||
color: #f5f8fa;
|
||||
font-weight: 750;
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.035em;
|
||||
}
|
||||
|
||||
.editor-styles-wrapper a {
|
||||
color: #74d5c7;
|
||||
}
|
||||
|
||||
.editor-styles-wrapper blockquote {
|
||||
padding-left: 1.5rem;
|
||||
color: #b8c5ce;
|
||||
border-left: 3px solid #74d5c7;
|
||||
}
|
||||
|
||||
.editor-styles-wrapper code,
|
||||
.editor-styles-wrapper pre {
|
||||
color: #d9fdf7;
|
||||
background: #040d16;
|
||||
border-radius: 8px;
|
||||
font-family: "SFMono-Regular", Consolas, monospace;
|
||||
}
|
||||
|
||||
.editor-styles-wrapper img {
|
||||
border-radius: 10px;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 492 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 38 KiB |
@@ -0,0 +1,273 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const ready = (callback) => {
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", callback, { once: true });
|
||||
return;
|
||||
}
|
||||
|
||||
callback();
|
||||
};
|
||||
|
||||
ready(function () {
|
||||
const header = document.querySelector(".site-header");
|
||||
const toggle = document.querySelector("[data-nav-toggle], .nav-toggle, .menu-toggle");
|
||||
const navigation = document.querySelector("[data-primary-nav], .primary-nav, .primary-navigation");
|
||||
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||
let lastFocusedElement = null;
|
||||
|
||||
const updateHeader = () => {
|
||||
if (!header) {
|
||||
return;
|
||||
}
|
||||
|
||||
header.classList.toggle("is-scrolled", window.scrollY > 24);
|
||||
};
|
||||
|
||||
updateHeader();
|
||||
window.addEventListener("scroll", updateHeader, { passive: true });
|
||||
|
||||
if (toggle && navigation) {
|
||||
const drawerClose = navigation.querySelector("[data-nav-close]");
|
||||
const navigationParent = navigation.parentNode;
|
||||
const navigationNextSibling = navigation.nextSibling;
|
||||
let navigationIsDrawer = false;
|
||||
|
||||
const placeNavigation = (asDrawer) => {
|
||||
if (asDrawer && !navigationIsDrawer) {
|
||||
document.body.appendChild(navigation);
|
||||
navigationIsDrawer = true;
|
||||
} else if (!asDrawer && navigationIsDrawer) {
|
||||
navigationParent.insertBefore(navigation, navigationNextSibling);
|
||||
navigationIsDrawer = false;
|
||||
}
|
||||
};
|
||||
|
||||
if (!navigation.id) {
|
||||
navigation.id = "site-navigation";
|
||||
}
|
||||
|
||||
toggle.setAttribute("aria-controls", navigation.id);
|
||||
toggle.setAttribute("aria-expanded", "false");
|
||||
navigation.setAttribute("aria-hidden", "true");
|
||||
|
||||
const focusableSelector = [
|
||||
"a[href]",
|
||||
"button:not([disabled])",
|
||||
"input:not([disabled])",
|
||||
"select:not([disabled])",
|
||||
"textarea:not([disabled])",
|
||||
"[tabindex]:not([tabindex='-1'])",
|
||||
].join(",");
|
||||
|
||||
const closeMenu = (restoreFocus) => {
|
||||
toggle.setAttribute("aria-expanded", "false");
|
||||
const toggleLabel = toggle.querySelector(".menu-toggle__label");
|
||||
if (toggleLabel && window.gustavooPortfolioNavigation) {
|
||||
toggleLabel.textContent = window.gustavooPortfolioNavigation.expand;
|
||||
}
|
||||
navigation.setAttribute("aria-hidden", "true");
|
||||
navigation.classList.remove("is-open");
|
||||
navigation.classList.remove("has-more-content");
|
||||
document.body.classList.remove("nav-open");
|
||||
|
||||
if (restoreFocus && lastFocusedElement) {
|
||||
lastFocusedElement.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const openMenu = () => {
|
||||
lastFocusedElement = document.activeElement;
|
||||
toggle.setAttribute("aria-expanded", "true");
|
||||
const toggleLabel = toggle.querySelector(".menu-toggle__label");
|
||||
if (toggleLabel && window.gustavooPortfolioNavigation) {
|
||||
toggleLabel.textContent = window.gustavooPortfolioNavigation.collapse;
|
||||
}
|
||||
navigation.setAttribute("aria-hidden", "false");
|
||||
navigation.classList.add("is-open");
|
||||
document.body.classList.add("nav-open");
|
||||
|
||||
window.requestAnimationFrame(function () {
|
||||
updateNavigationScrollHint();
|
||||
});
|
||||
|
||||
const firstFocusable = navigation.querySelector(focusableSelector);
|
||||
if (firstFocusable) {
|
||||
window.requestAnimationFrame(() => firstFocusable.focus());
|
||||
}
|
||||
};
|
||||
|
||||
const updateNavigationScrollHint = () => {
|
||||
const hasMoreContent = navigation.scrollHeight > navigation.clientHeight + 4
|
||||
&& navigation.scrollTop + navigation.clientHeight < navigation.scrollHeight - 4;
|
||||
navigation.classList.toggle("has-more-content", hasMoreContent);
|
||||
};
|
||||
|
||||
toggle.addEventListener("click", function () {
|
||||
if (toggle.getAttribute("aria-expanded") === "true") {
|
||||
closeMenu(false);
|
||||
} else {
|
||||
openMenu();
|
||||
}
|
||||
});
|
||||
|
||||
if (drawerClose) {
|
||||
drawerClose.addEventListener("click", function () {
|
||||
closeMenu(true);
|
||||
});
|
||||
}
|
||||
|
||||
navigation.addEventListener("click", function (event) {
|
||||
if (event.target.closest("a")) {
|
||||
closeMenu(false);
|
||||
}
|
||||
});
|
||||
|
||||
navigation.addEventListener("scroll", updateNavigationScrollHint, { passive: true });
|
||||
window.addEventListener("resize", updateNavigationScrollHint, { passive: true });
|
||||
|
||||
document.addEventListener("keydown", function (event) {
|
||||
if (toggle.getAttribute("aria-expanded") !== "true") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
closeMenu(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key !== "Tab") {
|
||||
return;
|
||||
}
|
||||
|
||||
const focusable = Array.from(navigation.querySelectorAll(focusableSelector));
|
||||
focusable.push(toggle);
|
||||
|
||||
if (!focusable.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const first = focusable[0];
|
||||
const last = focusable[focusable.length - 1];
|
||||
|
||||
if (event.shiftKey && document.activeElement === first) {
|
||||
event.preventDefault();
|
||||
last.focus();
|
||||
} else if (!event.shiftKey && document.activeElement === last) {
|
||||
event.preventDefault();
|
||||
first.focus();
|
||||
}
|
||||
});
|
||||
|
||||
const desktopQuery = window.matchMedia("(min-width: 921px)");
|
||||
const handleDesktop = (event) => {
|
||||
placeNavigation(!event.matches);
|
||||
|
||||
if (event.matches) {
|
||||
closeMenu(false);
|
||||
navigation.removeAttribute("aria-hidden");
|
||||
} else if (toggle.getAttribute("aria-expanded") !== "true") {
|
||||
navigation.setAttribute("aria-hidden", "true");
|
||||
}
|
||||
};
|
||||
|
||||
handleDesktop(desktopQuery);
|
||||
desktopQuery.addEventListener("change", handleDesktop);
|
||||
}
|
||||
|
||||
if (window.location.hash.length > 1) {
|
||||
const hashId = decodeURIComponent(window.location.hash.slice(1));
|
||||
const hashTarget = document.getElementById(hashId);
|
||||
|
||||
if (hashTarget) {
|
||||
window.requestAnimationFrame(function () {
|
||||
window.setTimeout(function () {
|
||||
hashTarget.scrollIntoView({ behavior: "auto", block: "start" });
|
||||
}, 120);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelectorAll('a[href^="#"]:not([href="#"])').forEach(function (link) {
|
||||
link.addEventListener("click", function (event) {
|
||||
const id = link.getAttribute("href").slice(1);
|
||||
const target = document.getElementById(id);
|
||||
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
// The header becomes fixed after the page is scrolled, so
|
||||
// scrollIntoView() cannot scroll the document when targeting it.
|
||||
if (id === "masthead") {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: reduceMotion.matches ? "auto" : "smooth",
|
||||
});
|
||||
} else {
|
||||
target.scrollIntoView({
|
||||
behavior: reduceMotion.matches ? "auto" : "smooth",
|
||||
block: "start",
|
||||
});
|
||||
}
|
||||
|
||||
if (window.history && window.history.replaceState) {
|
||||
window.history.replaceState(null, "", `#${id}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const revealItems = Array.from(document.querySelectorAll("[data-reveal]"));
|
||||
|
||||
if (revealItems.length && "IntersectionObserver" in window && !reduceMotion.matches) {
|
||||
document.documentElement.classList.add("has-reveal");
|
||||
|
||||
const revealObserver = new IntersectionObserver(
|
||||
function (entries, observer) {
|
||||
entries.forEach(function (entry) {
|
||||
if (!entry.isIntersecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
const delay = Number.parseInt(entry.target.dataset.revealDelay || "0", 10);
|
||||
if (delay > 0) {
|
||||
entry.target.style.transitionDelay = `${Math.min(delay, 500)}ms`;
|
||||
}
|
||||
|
||||
entry.target.classList.add("is-visible");
|
||||
observer.unobserve(entry.target);
|
||||
});
|
||||
},
|
||||
{ rootMargin: "0px 0px -8%", threshold: 0.12 }
|
||||
);
|
||||
|
||||
revealItems.forEach((item) => revealObserver.observe(item));
|
||||
} else {
|
||||
revealItems.forEach((item) => item.classList.add("is-visible"));
|
||||
}
|
||||
|
||||
const progress = document.querySelector("[data-reading-progress]");
|
||||
const article = document.querySelector(".entry-content");
|
||||
|
||||
if (progress && article) {
|
||||
const updateProgress = () => {
|
||||
const articleTop = article.getBoundingClientRect().top + window.scrollY;
|
||||
const articleHeight = article.offsetHeight;
|
||||
const viewportHeight = window.innerHeight;
|
||||
const distance = articleHeight - viewportHeight;
|
||||
const current = window.scrollY - articleTop;
|
||||
const percentage = distance > 0 ? Math.min(100, Math.max(0, (current / distance) * 100)) : 100;
|
||||
progress.style.setProperty("--reading-progress", `${percentage}%`);
|
||||
progress.setAttribute("aria-valuenow", String(Math.round(percentage)));
|
||||
};
|
||||
|
||||
updateProgress();
|
||||
window.addEventListener("scroll", updateProgress, { passive: true });
|
||||
window.addEventListener("resize", updateProgress, { passive: true });
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/** Category news archive. @package Gustavo_Portfolio */
|
||||
|
||||
get_header();
|
||||
?>
|
||||
<main id="primary" class="site-main news-index news-index--category">
|
||||
<header class="news-page-heading">
|
||||
<div class="site-shell">
|
||||
<h1><?php single_cat_title(); ?></h1>
|
||||
<?php if ( category_description() ) : ?><div class="news-page-heading__description"><?php echo wp_kses_post( category_description() ); ?></div><?php endif; ?>
|
||||
</div>
|
||||
</header>
|
||||
<div class="site-shell news-index__content">
|
||||
<?php if ( have_posts() ) : ?>
|
||||
<div class="latest-news-grid">
|
||||
<?php
|
||||
$gustavoo_news_index = 0;
|
||||
$gustavoo_displayed_post_ids = array();
|
||||
while ( have_posts() ) {
|
||||
the_post();
|
||||
$gustavoo_displayed_post_ids[] = get_the_ID();
|
||||
get_template_part( 'template-parts/global/latest-news-card', null, array( 'featured' => 0 === $gustavoo_news_index ) );
|
||||
$gustavoo_news_index++;
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php get_template_part( 'template-parts/global/latest-news-list', null, array( 'exclude' => $gustavoo_displayed_post_ids, 'category_id' => get_queried_object_id() ) ); ?>
|
||||
<?php gustavoo_portfolio_pagination(); ?>
|
||||
<?php else : ?>
|
||||
<?php get_template_part( 'template-parts/content/content', 'none' ); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</main>
|
||||
<?php get_footer(); ?>
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* Comments template.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
if ( post_password_required() ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
|
||||
<section id="comments" class="comments-area">
|
||||
<?php if ( have_comments() ) : ?>
|
||||
<h2 class="comments-title">
|
||||
<?php
|
||||
$gustavoo_comment_count = get_comments_number();
|
||||
printf(
|
||||
/* translators: 1: comment count, 2: post title. */
|
||||
esc_html( _n( '%1$s comentário em “%2$s”', '%1$s comentários em “%2$s”', $gustavoo_comment_count, 'gustavoo-portfolio' ) ),
|
||||
esc_html( number_format_i18n( $gustavoo_comment_count ) ),
|
||||
esc_html( get_the_title() )
|
||||
);
|
||||
?>
|
||||
</h2>
|
||||
|
||||
<ol class="comment-list">
|
||||
<?php
|
||||
wp_list_comments(
|
||||
array(
|
||||
'avatar_size' => 64,
|
||||
'short_ping' => true,
|
||||
'style' => 'ol',
|
||||
)
|
||||
);
|
||||
?>
|
||||
</ol>
|
||||
|
||||
<?php
|
||||
the_comments_navigation(
|
||||
array(
|
||||
'prev_text' => sprintf( '<span aria-hidden="true">←</span> %s', esc_html__( 'Comentários anteriores', 'gustavoo-portfolio' ) ),
|
||||
'next_text' => sprintf( '%s <span aria-hidden="true">→</span>', esc_html__( 'Próximos comentários', 'gustavoo-portfolio' ) ),
|
||||
)
|
||||
);
|
||||
?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( ! comments_open() && get_comments_number() ) : ?>
|
||||
<p class="no-comments"><?php esc_html_e( 'Os comentários estão encerrados.', 'gustavoo-portfolio' ); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
comment_form(
|
||||
array(
|
||||
'class_submit' => 'submit button button--primary',
|
||||
'label_submit' => __( 'Publicar comentário', 'gustavoo-portfolio' ),
|
||||
'title_reply' => __( 'Deixe um comentário', 'gustavoo-portfolio' ),
|
||||
'title_reply_before' => '<h2 id="reply-title" class="comment-reply-title">',
|
||||
'title_reply_after' => '</h2>',
|
||||
'comment_notes_before' => '<p class="comment-notes">' . esc_html__( 'Seu endereço de e-mail não será publicado. Campos obrigatórios são indicados.', 'gustavoo-portfolio' ) . '</p>',
|
||||
)
|
||||
);
|
||||
?>
|
||||
</section>
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/**
|
||||
* Site footer and the global newsletter CTA.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
get_template_part( 'template-parts/global/newsletter-cta' );
|
||||
?>
|
||||
|
||||
<footer id="colophon" class="site-footer">
|
||||
<div class="site-shell site-footer__main">
|
||||
<div class="site-footer__brand">
|
||||
<div class="site-footer__brand-row">
|
||||
<?php gustavoo_portfolio_the_brand(); ?>
|
||||
<p class="site-footer__name"><?php echo esc_html( get_bloginfo( 'name' ) ); ?></p>
|
||||
</div>
|
||||
<p><?php echo esc_html( gustavoo_portfolio_get_setting( 'hero_description' ) ); ?></p>
|
||||
<?php gustavoo_portfolio_social_links( 'social-links social-links--footer' ); ?>
|
||||
</div>
|
||||
|
||||
<?php if ( has_nav_menu( 'footer' ) ) : ?>
|
||||
<nav class="footer-navigation" aria-label="<?php esc_attr_e( 'Navegação do rodapé', 'gustavoo-portfolio' ); ?>">
|
||||
<?php
|
||||
wp_nav_menu(
|
||||
array(
|
||||
'theme_location' => 'footer',
|
||||
'menu_class' => 'footer-menu',
|
||||
'container' => false,
|
||||
'depth' => 1,
|
||||
'fallback_cb' => false,
|
||||
)
|
||||
);
|
||||
?>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="site-shell site-footer__bottom">
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: 1: current year, 2: site name. */
|
||||
esc_html__( '© %1$s %2$s. Todos os direitos reservados.', 'gustavoo-portfolio' ),
|
||||
esc_html( wp_date( 'Y' ) ),
|
||||
esc_html( get_bloginfo( 'name' ) )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<a href="#masthead" class="back-to-top"><?php esc_html_e( 'Voltar ao topo', 'gustavoo-portfolio' ); ?> <span aria-hidden="true">↑</span></a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<?php
|
||||
if ( is_front_page() ) {
|
||||
gustavoo_portfolio_the_whatsapp_float();
|
||||
}
|
||||
?>
|
||||
|
||||
<?php wp_footer(); ?>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* Portfolio landing page.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
get_header();
|
||||
?>
|
||||
|
||||
<main id="primary" class="site-main front-page">
|
||||
<?php get_template_part( 'template-parts/front/hero' ); ?>
|
||||
<?php get_template_part( 'template-parts/front/about' ); ?>
|
||||
<?php get_template_part( 'template-parts/front/services' ); ?>
|
||||
<?php get_template_part( 'template-parts/front/projects' ); ?>
|
||||
|
||||
<?php if ( is_page() && have_posts() ) : ?>
|
||||
<?php
|
||||
while ( have_posts() ) {
|
||||
the_post();
|
||||
$gustavoo_front_content = trim( (string) get_the_content() );
|
||||
|
||||
if ( $gustavoo_front_content ) {
|
||||
?>
|
||||
<div class="section section--custom-content">
|
||||
<div class="site-shell entry-content">
|
||||
<?php the_content(); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php get_template_part( 'template-parts/front/contact' ); ?>
|
||||
<?php get_template_part( 'template-parts/front/posts' ); ?>
|
||||
</main>
|
||||
|
||||
<?php
|
||||
get_footer();
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* Gustavo Portfolio theme bootstrap.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$gustavoo_portfolio_includes = array(
|
||||
'/inc/defaults.php',
|
||||
'/inc/setup.php',
|
||||
'/inc/enqueue.php',
|
||||
'/inc/template-tags.php',
|
||||
'/inc/customizer.php',
|
||||
'/inc/forms.php',
|
||||
'/inc/widgets.php',
|
||||
);
|
||||
|
||||
foreach ( $gustavoo_portfolio_includes as $gustavoo_portfolio_file ) {
|
||||
$gustavoo_portfolio_path = get_theme_file_path( $gustavoo_portfolio_file );
|
||||
|
||||
if ( file_exists( $gustavoo_portfolio_path ) ) {
|
||||
require_once $gustavoo_portfolio_path;
|
||||
}
|
||||
}
|
||||
|
||||
unset( $gustavoo_portfolio_file, $gustavoo_portfolio_includes, $gustavoo_portfolio_path );
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* Site header.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
?><!doctype html>
|
||||
<html <?php language_attributes(); ?>>
|
||||
<head>
|
||||
<meta charset="<?php bloginfo( 'charset' ); ?>">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<?php wp_head(); ?>
|
||||
</head>
|
||||
<body <?php body_class(); ?>>
|
||||
<?php wp_body_open(); ?>
|
||||
<a class="skip-link screen-reader-text" href="#primary"><?php esc_html_e( 'Pular para o conteúdo', 'gustavoo-portfolio' ); ?></a>
|
||||
|
||||
<?php
|
||||
$gustavoo_is_editorial_page = is_home() || is_category() || is_singular( 'post' );
|
||||
$gustavoo_primary_shortcut_attr = is_customize_preview() ? ' data-customize-partial-id="gustavoo_portfolio_menu_shortcut_primary"' : '';
|
||||
?>
|
||||
<header id="masthead" class="site-header<?php echo is_front_page() ? ' site-header--overlay' : ''; ?><?php echo $gustavoo_is_editorial_page ? ' site-header--editorial' : ''; ?>">
|
||||
<div class="site-shell site-header__inner">
|
||||
<div class="site-branding">
|
||||
<?php gustavoo_portfolio_the_brand(); ?>
|
||||
<a class="site-branding__text" href="<?php echo esc_url( home_url( '/' ) ); ?>" rel="home">
|
||||
<span class="site-branding__name"><?php echo esc_html( get_bloginfo( 'name' ) ); ?></span>
|
||||
<span class="site-branding__tagline site-branding__tagline--full"><?php esc_html_e( 'Desenvolvedor Full Stack | Web, Mobile, Infraestrutura & Automação', 'gustavoo-portfolio' ); ?></span>
|
||||
<span class="site-branding__tagline site-branding__tagline--short"><?php esc_html_e( 'Desenvolvedor Full Stack | Web, Mobile, Infraestrutura & Automação', 'gustavoo-portfolio' ); ?></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<button class="menu-toggle" type="button" aria-expanded="false" aria-controls="primary-menu">
|
||||
<span class="menu-toggle__label"><?php esc_html_e( 'Abrir menu', 'gustavoo-portfolio' ); ?></span>
|
||||
<span class="menu-toggle__icon" aria-hidden="true"><span></span><span></span><span></span></span>
|
||||
</button>
|
||||
|
||||
<nav id="site-navigation" class="primary-navigation" aria-label="<?php esc_attr_e( 'Navegação principal', 'gustavoo-portfolio' ); ?>"<?php echo $gustavoo_primary_shortcut_attr; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static attribute. ?>>
|
||||
<button class="menu-drawer-close" type="button" data-nav-close aria-label="<?php esc_attr_e( 'Fechar menu', 'gustavoo-portfolio' ); ?>">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</button>
|
||||
<?php
|
||||
wp_nav_menu(
|
||||
array(
|
||||
'theme_location' => 'primary',
|
||||
'menu_id' => 'primary-menu',
|
||||
'menu_class' => 'primary-menu',
|
||||
'container' => false,
|
||||
'fallback_cb' => 'gustavoo_portfolio_primary_menu_fallback',
|
||||
'depth' => 2,
|
||||
)
|
||||
);
|
||||
?>
|
||||
<?php if ( $gustavoo_is_editorial_page ) : ?>
|
||||
<?php gustavoo_portfolio_mobile_news_category_menu(); ?>
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<?php
|
||||
if ( $gustavoo_is_editorial_page ) {
|
||||
gustavoo_portfolio_news_category_menu();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* Posts page.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
get_header();
|
||||
|
||||
?>
|
||||
|
||||
<main id="primary" class="site-main news-index">
|
||||
<header class="news-page-heading"><div class="site-shell"><h1><?php esc_html_e( 'Últimas notícias', 'gustavoo-portfolio' ); ?></h1></div></header>
|
||||
|
||||
<div class="site-shell news-index__content">
|
||||
<?php if ( have_posts() ) : ?>
|
||||
<div class="latest-news-grid">
|
||||
<?php
|
||||
$gustavoo_news_index = 0;
|
||||
$gustavoo_displayed_post_ids = array();
|
||||
while ( have_posts() ) {
|
||||
the_post();
|
||||
$gustavoo_displayed_post_ids[] = get_the_ID();
|
||||
get_template_part( 'template-parts/global/latest-news-card', null, array( 'featured' => 0 === $gustavoo_news_index ) );
|
||||
$gustavoo_news_index++;
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php get_template_part( 'template-parts/global/latest-news-list', null, array( 'exclude' => $gustavoo_displayed_post_ids ) ); ?>
|
||||
<?php gustavoo_portfolio_pagination(); ?>
|
||||
<?php else : ?>
|
||||
<?php get_template_part( 'template-parts/content/content', 'none' ); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php
|
||||
get_footer();
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
/**
|
||||
* Native WordPress Customizer integration and selective-refresh shortcuts.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/** Sanitize a URL or an in-page anchor used by theme calls to action. */
|
||||
function gustavoo_portfolio_customize_link( $value ) {
|
||||
$value = trim( sanitize_text_field( (string) $value ) );
|
||||
|
||||
if ( preg_match( '/^#[A-Za-z][A-Za-z0-9_:.-]*$/', $value ) ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return esc_url_raw( $value, array( 'http', 'https', 'mailto', 'tel' ) );
|
||||
}
|
||||
|
||||
/** Render a template part for a selective-refresh partial. */
|
||||
function gustavoo_portfolio_customize_render_part( $slug ) {
|
||||
ob_start();
|
||||
get_template_part( $slug );
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
|
||||
/** Register the portfolio content controls in Appearance > Customize. */
|
||||
function gustavoo_portfolio_customize_register( $wp_customize ) {
|
||||
$wp_customize->add_panel(
|
||||
'gustavoo_portfolio_content',
|
||||
array(
|
||||
'title' => __( 'Conteúdo do tema', 'gustavoo-portfolio' ),
|
||||
'description' => __( 'Edite os conteúdos exibidos no site e use os lápis na prévia para ir direto à seção desejada.', 'gustavoo-portfolio' ),
|
||||
'priority' => 30,
|
||||
)
|
||||
);
|
||||
|
||||
$sections = array(
|
||||
'hero' => __( 'Hero', 'gustavoo-portfolio' ),
|
||||
'about' => __( 'Sobre', 'gustavoo-portfolio' ),
|
||||
'services' => __( 'Serviços', 'gustavoo-portfolio' ),
|
||||
'projects' => __( 'Projetos', 'gustavoo-portfolio' ),
|
||||
'blog' => __( 'Blog', 'gustavoo-portfolio' ),
|
||||
'contact' => __( 'Contato', 'gustavoo-portfolio' ),
|
||||
'newsletter' => __( 'Newsletter', 'gustavoo-portfolio' ),
|
||||
'social' => __( 'Redes sociais', 'gustavoo-portfolio' ),
|
||||
);
|
||||
|
||||
foreach ( $sections as $id => $title ) {
|
||||
$wp_customize->add_section(
|
||||
'gustavoo_portfolio_' . $id,
|
||||
array(
|
||||
'title' => $title,
|
||||
'panel' => 'gustavoo_portfolio_content',
|
||||
'priority' => 10,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$fields = array(
|
||||
'hero_eyebrow' => array( 'hero', __( 'Sobretítulo', 'gustavoo-portfolio' ), 'text' ),
|
||||
'hero_title' => array( 'hero', __( 'Título', 'gustavoo-portfolio' ), 'text' ),
|
||||
'hero_description' => array( 'hero', __( 'Descrição', 'gustavoo-portfolio' ), 'textarea' ),
|
||||
'hero_primary_label' => array( 'hero', __( 'Botão principal — texto', 'gustavoo-portfolio' ), 'text' ),
|
||||
'hero_primary_url' => array( 'hero', __( 'Botão principal — link', 'gustavoo-portfolio' ), 'link' ),
|
||||
'hero_secondary_label' => array( 'hero', __( 'Botão secundário — texto', 'gustavoo-portfolio' ), 'text' ),
|
||||
'hero_secondary_url' => array( 'hero', __( 'Botão secundário — link', 'gustavoo-portfolio' ), 'link' ),
|
||||
'about_eyebrow' => array( 'about', __( 'Sobretítulo', 'gustavoo-portfolio' ), 'text' ),
|
||||
'about_title' => array( 'about', __( 'Título', 'gustavoo-portfolio' ), 'text' ),
|
||||
'about_text' => array( 'about', __( 'Texto principal', 'gustavoo-portfolio' ), 'textarea' ),
|
||||
'about_secondary_text' => array( 'about', __( 'Texto complementar', 'gustavoo-portfolio' ), 'textarea' ),
|
||||
'services_eyebrow' => array( 'services', __( 'Sobretítulo', 'gustavoo-portfolio' ), 'text' ),
|
||||
'services_title' => array( 'services', __( 'Título', 'gustavoo-portfolio' ), 'text' ),
|
||||
'services_description' => array( 'services', __( 'Descrição', 'gustavoo-portfolio' ), 'textarea' ),
|
||||
'projects_eyebrow' => array( 'projects', __( 'Sobretítulo', 'gustavoo-portfolio' ), 'text' ),
|
||||
'projects_title' => array( 'projects', __( 'Título', 'gustavoo-portfolio' ), 'text' ),
|
||||
'projects_text' => array( 'projects', __( 'Descrição', 'gustavoo-portfolio' ), 'textarea' ),
|
||||
'projects_limit' => array( 'projects', __( 'Quantidade de projetos', 'gustavoo-portfolio' ), 'number' ),
|
||||
'blog_eyebrow' => array( 'blog', __( 'Sobretítulo', 'gustavoo-portfolio' ), 'text' ),
|
||||
'blog_title' => array( 'blog', __( 'Título', 'gustavoo-portfolio' ), 'text' ),
|
||||
'blog_text' => array( 'blog', __( 'Descrição', 'gustavoo-portfolio' ), 'textarea' ),
|
||||
'blog_limit' => array( 'blog', __( 'Quantidade de posts', 'gustavoo-portfolio' ), 'number' ),
|
||||
'contact_eyebrow' => array( 'contact', __( 'Sobretítulo', 'gustavoo-portfolio' ), 'text' ),
|
||||
'contact_heading' => array( 'contact', __( 'Título', 'gustavoo-portfolio' ), 'text' ),
|
||||
'contact_text' => array( 'contact', __( 'Descrição', 'gustavoo-portfolio' ), 'textarea' ),
|
||||
'email' => array( 'contact', __( 'E-mail', 'gustavoo-portfolio' ), 'email' ),
|
||||
'whatsapp' => array( 'contact', __( 'WhatsApp — número', 'gustavoo-portfolio' ), 'text' ),
|
||||
'whatsapp_message' => array( 'contact', __( 'WhatsApp — mensagem inicial', 'gustavoo-portfolio' ), 'textarea' ),
|
||||
'newsletter_eyebrow' => array( 'newsletter', __( 'Sobretítulo', 'gustavoo-portfolio' ), 'text' ),
|
||||
'newsletter_heading' => array( 'newsletter', __( 'Título', 'gustavoo-portfolio' ), 'text' ),
|
||||
'newsletter_text' => array( 'newsletter', __( 'Descrição', 'gustavoo-portfolio' ), 'textarea' ),
|
||||
'github_url' => array( 'social', __( 'GitHub', 'gustavoo-portfolio' ), 'url' ),
|
||||
'linkedin_url' => array( 'social', __( 'LinkedIn', 'gustavoo-portfolio' ), 'url' ),
|
||||
'instagram_url' => array( 'social', __( 'Instagram', 'gustavoo-portfolio' ), 'url' ),
|
||||
'availability_label' => array( 'social', __( 'Status de disponibilidade', 'gustavoo-portfolio' ), 'text' ),
|
||||
);
|
||||
|
||||
foreach ( $fields as $key => $field ) {
|
||||
$setting_id = 'gustavoo_portfolio_' . $key;
|
||||
$sanitize = 'textarea' === $field[2] ? 'sanitize_textarea_field' : ( 'number' === $field[2] ? 'absint' : ( in_array( $field[2], array( 'url', 'link' ), true ) ? 'gustavoo_portfolio_customize_link' : ( 'email' === $field[2] ? 'sanitize_email' : 'sanitize_text_field' ) ) );
|
||||
|
||||
$wp_customize->add_setting(
|
||||
$setting_id,
|
||||
array(
|
||||
'default' => gustavoo_portfolio_get_setting( $key ),
|
||||
'sanitize_callback' => $sanitize,
|
||||
// Each front-page section is refreshed by its native Customizer
|
||||
// partial, allowing WordPress to expose its own edit shortcut.
|
||||
'transport' => 'postMessage',
|
||||
)
|
||||
);
|
||||
|
||||
$control_args = array(
|
||||
'label' => $field[1],
|
||||
'section' => 'gustavoo_portfolio_' . $field[0],
|
||||
'type' => in_array( $field[2], array( 'textarea', 'number', 'email', 'url' ), true ) ? $field[2] : 'text',
|
||||
);
|
||||
|
||||
if ( 'number' === $field[2] ) {
|
||||
$control_args['input_attrs'] = array( 'min' => 1, 'max' => 24 );
|
||||
}
|
||||
|
||||
$wp_customize->add_control( $setting_id, $control_args );
|
||||
}
|
||||
|
||||
if ( ! isset( $wp_customize->selective_refresh ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$partials = array(
|
||||
'hero' => array( '.hero-stage', 'template-parts/front/hero', array( 'hero_eyebrow', 'hero_title', 'hero_description', 'hero_primary_label', 'hero_primary_url', 'hero_secondary_label', 'hero_secondary_url' ) ),
|
||||
'about' => array( '#sobre', 'template-parts/front/about', array( 'about_eyebrow', 'about_title', 'about_text', 'about_secondary_text' ) ),
|
||||
'services' => array( '#servicos', 'template-parts/front/services', array( 'services_eyebrow', 'services_title', 'services_description' ) ),
|
||||
'projects' => array( '#projetos', 'template-parts/front/projects', array( 'projects_eyebrow', 'projects_title', 'projects_text', 'projects_limit' ) ),
|
||||
'blog' => array( '#blog', 'template-parts/front/posts', array( 'blog_eyebrow', 'blog_title', 'blog_text', 'blog_limit' ) ),
|
||||
'contact' => array( '#contato', 'template-parts/front/contact', array( 'contact_eyebrow', 'contact_heading', 'contact_text', 'email', 'whatsapp', 'whatsapp_message', 'github_url', 'linkedin_url', 'instagram_url' ) ),
|
||||
);
|
||||
|
||||
foreach ( $partials as $id => $partial ) {
|
||||
$settings = array_map(
|
||||
static function ( $key ) {
|
||||
return 'gustavoo_portfolio_' . $key;
|
||||
},
|
||||
$partial[2]
|
||||
);
|
||||
|
||||
/*
|
||||
* A native edit shortcut is tied to the partial's primary setting.
|
||||
* Using the first actual setting as the partial ID makes the pencil
|
||||
* focus a real control instead of an unresolvable synthetic partial.
|
||||
*/
|
||||
$wp_customize->selective_refresh->add_partial(
|
||||
$settings[0],
|
||||
array(
|
||||
'selector' => $partial[0],
|
||||
'settings' => $settings,
|
||||
'primary_setting' => $settings[0],
|
||||
'container_inclusive' => true,
|
||||
'render_callback' => static function () use ( $partial ) {
|
||||
return gustavoo_portfolio_customize_render_part( $partial[1] );
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$wp_customize->selective_refresh->add_partial(
|
||||
'gustavoo_portfolio_whatsapp_shortcut',
|
||||
array(
|
||||
'selector' => '.whatsapp-float-wrap',
|
||||
'settings' => array( 'gustavoo_portfolio_whatsapp', 'gustavoo_portfolio_whatsapp_message' ),
|
||||
'primary_setting' => 'gustavoo_portfolio_whatsapp',
|
||||
'container_inclusive' => true,
|
||||
'render_callback' => static function () {
|
||||
return gustavoo_portfolio_customize_render_part( 'template-parts/global/whatsapp-float' );
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
add_action( 'customize_register', 'gustavoo_portfolio_customize_register' );
|
||||
|
||||
/** Register native Customizer shortcuts for the theme's menu locations. */
|
||||
function gustavoo_portfolio_customize_menu_shortcuts( $wp_customize ) {
|
||||
if ( ! isset( $wp_customize->selective_refresh ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$menus = array(
|
||||
'primary' => '#site-navigation',
|
||||
'news_categories' => '.news-category-nav',
|
||||
);
|
||||
|
||||
foreach ( $menus as $location => $selector ) {
|
||||
$setting = 'nav_menu_locations[' . $location . ']';
|
||||
|
||||
if ( ! $wp_customize->get_setting( $setting ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$wp_customize->selective_refresh->add_partial(
|
||||
'gustavoo_portfolio_menu_shortcut_' . $location,
|
||||
array(
|
||||
'selector' => $selector,
|
||||
'settings' => array( $setting ),
|
||||
'primary_setting' => $setting,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
add_action( 'customize_register', 'gustavoo_portfolio_customize_menu_shortcuts', 20 );
|
||||
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
/**
|
||||
* Theme setting defaults and reusable portfolio content.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return default settings without writing options to the database.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
function gustavoo_portfolio_get_defaults() {
|
||||
$defaults = array(
|
||||
'hero_eyebrow' => __( 'Desenvolvimento de ponta a ponta', 'gustavoo-portfolio' ),
|
||||
'hero_title' => __( 'Transformo ideias em produtos digitais sólidos.', 'gustavoo-portfolio' ),
|
||||
'hero_description' => __( 'Gustavo, Desenvolvedor Full Stack Sênior. Web, mobile, infraestrutura e automação para tirar projetos do papel e fazê-los crescer.', 'gustavoo-portfolio' ),
|
||||
'hero_primary_label' => __( 'Ver projetos', 'gustavoo-portfolio' ),
|
||||
'hero_primary_url' => '#projetos',
|
||||
'hero_secondary_label' => __( 'Fale comigo', 'gustavoo-portfolio' ),
|
||||
'hero_secondary_url' => '#contato',
|
||||
'about_eyebrow' => __( 'Sobre mim', 'gustavoo-portfolio' ),
|
||||
'about_title' => __( 'Responsabilidade técnica do planejamento à produção.', 'gustavoo-portfolio' ),
|
||||
'about_text' => __( 'Mais do que escrever código, meu objetivo é garantir que seu projeto saia do papel e funcione exatamente como planejado. Assumo a responsabilidade técnica de ponta a ponta para que você foque no que importa: crescer.', 'gustavoo-portfolio' ),
|
||||
'about_secondary_text' => __( 'Já participei do desenvolvimento e lançamento de múltiplos projetos digitais, atuando da arquitetura técnica à divulgação e ao crescimento das plataformas. Essa combinação de visão técnica e entendimento de mercado orienta cada entrega.', 'gustavoo-portfolio' ),
|
||||
'services_eyebrow' => __( 'Como posso ajudar', 'gustavoo-portfolio' ),
|
||||
'services_title' => __( 'Soluções completas para produtos digitais.', 'gustavoo-portfolio' ),
|
||||
'services_description' => __( 'Estratégia, código e infraestrutura trabalhando juntos para criar experiências rápidas, seguras e preparadas para evoluir.', 'gustavoo-portfolio' ),
|
||||
'projects_eyebrow' => __( 'Portfólio', 'gustavoo-portfolio' ),
|
||||
'projects_title' => __( 'Projetos selecionados', 'gustavoo-portfolio' ),
|
||||
'projects_text' => __( 'Uma seleção de produtos, experiências e soluções que ajudei a colocar no mundo.', 'gustavoo-portfolio' ),
|
||||
'projects_limit' => 6,
|
||||
'blog_eyebrow' => __( 'Blog', 'gustavoo-portfolio' ),
|
||||
'blog_title' => __( 'Código, produto e bastidores', 'gustavoo-portfolio' ),
|
||||
'blog_text' => __( 'Análises práticas sobre desenvolvimento, infraestrutura, automação e crescimento de produtos digitais.', 'gustavoo-portfolio' ),
|
||||
'blog_limit' => 4,
|
||||
'contact_eyebrow' => __( 'Vamos conversar', 'gustavoo-portfolio' ),
|
||||
'contact_heading' => __( 'Tem um projeto em mente?', 'gustavoo-portfolio' ),
|
||||
'contact_text' => __( 'Conte o que você precisa construir ou melhorar. Responderei com os próximos passos para transformar a ideia em uma entrega concreta.', 'gustavoo-portfolio' ),
|
||||
'contact_form_id' => 0,
|
||||
'newsletter_eyebrow' => __( 'Newsletter', 'gustavoo-portfolio' ),
|
||||
'newsletter_heading' => __( 'Ideias úteis, direto na sua caixa de entrada.', 'gustavoo-portfolio' ),
|
||||
'newsletter_text' => __( 'Receba conteúdos sobre desenvolvimento, produto, infraestrutura e automação. Sem ruído e sem spam.', 'gustavoo-portfolio' ),
|
||||
'newsletter_form_id' => 0,
|
||||
'email' => (string) get_option( 'admin_email', '' ),
|
||||
'whatsapp' => '',
|
||||
'whatsapp_message' => __( 'Olá! Gostaria de falar sobre um projeto.', 'gustavoo-portfolio' ),
|
||||
'github_url' => '',
|
||||
'linkedin_url' => '',
|
||||
'instagram_url' => '',
|
||||
'projects_archive_url' => '',
|
||||
'availability_label' => __( 'Disponível para novos projetos', 'gustavoo-portfolio' ),
|
||||
);
|
||||
|
||||
/**
|
||||
* Filter theme defaults before saved settings are merged.
|
||||
*
|
||||
* @param array<string, mixed> $defaults Default settings.
|
||||
*/
|
||||
return apply_filters( 'gustavoo_portfolio_defaults', $defaults );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the saved theme settings merged with safe local defaults.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
function gustavoo_portfolio_get_settings() {
|
||||
$settings = get_option( 'gustavoo_portfolio_settings', array() );
|
||||
|
||||
if ( ! is_array( $settings ) ) {
|
||||
$settings = array();
|
||||
}
|
||||
|
||||
return wp_parse_args( $settings, gustavoo_portfolio_get_defaults() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read one theme setting. A few aliases preserve compatibility with early builds.
|
||||
*
|
||||
* @param string $key Setting key.
|
||||
* @param mixed $fallback Optional fallback.
|
||||
* @return mixed
|
||||
*/
|
||||
function gustavoo_portfolio_get_setting( $key, $fallback = null ) {
|
||||
$customizer_key = 'gustavoo_portfolio_' . $key;
|
||||
$customizer_values = get_theme_mods();
|
||||
|
||||
if ( is_array( $customizer_values ) && array_key_exists( $customizer_key, $customizer_values ) ) {
|
||||
return $customizer_values[ $customizer_key ];
|
||||
}
|
||||
|
||||
$settings = gustavoo_portfolio_get_settings();
|
||||
$aliases = array(
|
||||
'hero_eyebrow' => array( 'hero_kicker' ),
|
||||
'hero_description' => array( 'hero_text' ),
|
||||
'contact_form_id' => array( 'fluent_contact_form_id', 'lead_form_id' ),
|
||||
'newsletter_form_id' => array( 'fluent_newsletter_form_id', 'fluentcrm_form_id' ),
|
||||
'contact_heading' => array( 'contact_title' ),
|
||||
'newsletter_heading' => array( 'newsletter_title' ),
|
||||
);
|
||||
|
||||
if ( array_key_exists( $key, $settings ) && '' !== $settings[ $key ] && null !== $settings[ $key ] ) {
|
||||
return $settings[ $key ];
|
||||
}
|
||||
|
||||
if ( isset( $aliases[ $key ] ) ) {
|
||||
foreach ( $aliases[ $key ] as $alias ) {
|
||||
if ( array_key_exists( $alias, $settings ) && '' !== $settings[ $alias ] && null !== $settings[ $alias ] ) {
|
||||
return $settings[ $alias ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( null !== $fallback ) {
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
$defaults = gustavoo_portfolio_get_defaults();
|
||||
|
||||
return $defaults[ $key ] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the service cards used by the front page.
|
||||
*
|
||||
* @return array<int, array<string, string>>
|
||||
*/
|
||||
function gustavoo_portfolio_get_services() {
|
||||
$services = array(
|
||||
array(
|
||||
'number' => '01',
|
||||
'title' => __( 'Web, Mobile e E-commerce', 'gustavoo-portfolio' ),
|
||||
'description' => __( 'Sites institucionais, aplicações para iOS e Android, softwares desktop, lojas e marketplaces personalizados com foco em experiência e conversão.', 'gustavoo-portfolio' ),
|
||||
),
|
||||
array(
|
||||
'number' => '02',
|
||||
'title' => __( 'WordPress avançado', 'gustavoo-portfolio' ),
|
||||
'description' => __( 'Temas e plugins sob medida, integrações específicas, recuperação de sites e otimização para Core Web Vitals.', 'gustavoo-portfolio' ),
|
||||
),
|
||||
array(
|
||||
'number' => '03',
|
||||
'title' => __( 'Infraestrutura e servidores', 'gustavoo-portfolio' ),
|
||||
'description' => __( 'Ambientes Linux, VPS e servidores dedicados configurados para manter aplicações estáveis, rápidas, seguras e escaláveis.', 'gustavoo-portfolio' ),
|
||||
),
|
||||
array(
|
||||
'number' => '04',
|
||||
'title' => __( 'Automação e integrações', 'gustavoo-portfolio' ),
|
||||
'description' => __( 'APIs e fluxos com n8n para conectar sistemas, organizar dados e eliminar tarefas operacionais repetitivas.', 'gustavoo-portfolio' ),
|
||||
),
|
||||
array(
|
||||
'number' => '05',
|
||||
'title' => __( 'Games e experiências interativas', 'gustavoo-portfolio' ),
|
||||
'description' => __( 'Jogos 2D e 3D para celulares, computadores e navegadores, incluindo ranking, multiplayer e compras dentro do jogo.', 'gustavoo-portfolio' ),
|
||||
),
|
||||
array(
|
||||
'number' => '06',
|
||||
'title' => __( 'Divulgação e crescimento', 'gustavoo-portfolio' ),
|
||||
'description' => __( 'Estratégias de conteúdo, comunidades, parcerias e mídia paga para aproximar produtos digitais do público certo.', 'gustavoo-portfolio' ),
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Filter the service cards shown on the front page.
|
||||
*
|
||||
* @param array<int, array<string, string>> $services Service cards.
|
||||
*/
|
||||
return apply_filters( 'gustavoo_portfolio_services', $services );
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* Front-end assets.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a cache-busting asset version.
|
||||
*
|
||||
* @param string $relative_path Theme-relative path.
|
||||
* @return string
|
||||
*/
|
||||
function gustavoo_portfolio_asset_version( $relative_path ) {
|
||||
$release = '1.1.0';
|
||||
$path = get_theme_file_path( '/' . ltrim( $relative_path, '/' ) );
|
||||
|
||||
if ( file_exists( $path ) ) {
|
||||
return $release . '-' . (string) filemtime( $path );
|
||||
}
|
||||
|
||||
return $release;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue only local, versioned assets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function gustavoo_portfolio_enqueue_assets() {
|
||||
wp_enqueue_style(
|
||||
'gustavoo-portfolio-style',
|
||||
get_stylesheet_uri(),
|
||||
array(),
|
||||
gustavoo_portfolio_asset_version( 'style.css' )
|
||||
);
|
||||
|
||||
if ( file_exists( get_theme_file_path( '/assets/css/main.css' ) ) ) {
|
||||
wp_enqueue_style(
|
||||
'gustavoo-portfolio-main',
|
||||
get_theme_file_uri( '/assets/css/main.css' ),
|
||||
array( 'gustavoo-portfolio-style' ),
|
||||
gustavoo_portfolio_asset_version( 'assets/css/main.css' )
|
||||
);
|
||||
}
|
||||
|
||||
if ( file_exists( get_theme_file_path( '/assets/js/navigation.js' ) ) ) {
|
||||
wp_enqueue_script(
|
||||
'gustavoo-portfolio-navigation',
|
||||
get_theme_file_uri( '/assets/js/navigation.js' ),
|
||||
array(),
|
||||
gustavoo_portfolio_asset_version( 'assets/js/navigation.js' ),
|
||||
true
|
||||
);
|
||||
|
||||
wp_script_add_data( 'gustavoo-portfolio-navigation', 'strategy', 'defer' );
|
||||
wp_localize_script(
|
||||
'gustavoo-portfolio-navigation',
|
||||
'gustavooPortfolioNavigation',
|
||||
array(
|
||||
'expand' => __( 'Abrir menu', 'gustavoo-portfolio' ),
|
||||
'collapse' => __( 'Fechar menu', 'gustavoo-portfolio' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
|
||||
wp_enqueue_script( 'comment-reply' );
|
||||
}
|
||||
}
|
||||
add_action( 'wp_enqueue_scripts', 'gustavoo_portfolio_enqueue_assets' );
|
||||
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
/**
|
||||
* Fluent Forms presentation helpers.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the configured contact form contains the fields required by the site.
|
||||
*
|
||||
* This is intentionally idempotent so forms created by an older seed version
|
||||
* are repaired even when the seeder state is already marked complete.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function gustavoo_portfolio_ensure_contact_form_fields() {
|
||||
$form_id = absint( gustavoo_portfolio_get_setting( 'contact_form_id' ) );
|
||||
|
||||
if ( ! $form_id || ! class_exists( '\FluentForm\App\Models\Form' ) || ! function_exists( 'fluentformLoadFile' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$form = \FluentForm\App\Models\Form::find( $form_id );
|
||||
|
||||
if ( ! $form ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$structure = json_decode( (string) $form->form_fields, true );
|
||||
|
||||
if ( ! is_array( $structure ) || ! isset( $structure['fields'] ) || ! is_array( $structure['fields'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$defaults = fluentformLoadFile( 'Services/FormBuilder/DefaultElements.php' );
|
||||
$message = $defaults['general']['textarea'] ?? array();
|
||||
|
||||
if ( ! $message ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fields_by_name = array();
|
||||
$changed = false;
|
||||
|
||||
foreach ( $structure['fields'] as $field ) {
|
||||
$name = (string) ( $field['attributes']['name'] ?? '' );
|
||||
|
||||
if ( ! in_array( $name, array( 'email', 'whatsapp', 'message' ), true ) ) {
|
||||
$changed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( isset( $fields_by_name[ $name ] ) ) {
|
||||
$changed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
$fields_by_name[ $name ] = $field;
|
||||
}
|
||||
|
||||
if ( isset( $fields_by_name['email'] ) ) {
|
||||
$fields_by_name['email']['settings']['validation_rules']['required']['value'] = true;
|
||||
}
|
||||
|
||||
if ( isset( $fields_by_name['whatsapp'] ) ) {
|
||||
$whatsapp = $fields_by_name['whatsapp'];
|
||||
$whatsapp['settings']['validation_rules']['required']['value'] = true;
|
||||
$whatsapp['settings']['temp_mask'] = 'custom';
|
||||
$whatsapp['settings']['data-mask-reverse'] = 'no';
|
||||
$whatsapp['attributes']['data-mask'] = '(00) 00000-0000';
|
||||
$fields_by_name['whatsapp'] = $whatsapp;
|
||||
}
|
||||
|
||||
if ( ! isset( $fields_by_name['whatsapp'] ) ) {
|
||||
$whatsapp = $defaults['general']['input_mask'] ?? array();
|
||||
if ( $whatsapp ) {
|
||||
$whatsapp['uniqElKey'] = 'el_gso_contact_whatsapp';
|
||||
$whatsapp['attributes']['name'] = 'whatsapp';
|
||||
$whatsapp['attributes']['placeholder'] = '(31) 99999-9999';
|
||||
$whatsapp['attributes']['data-mask'] = '(00) 00000-0000';
|
||||
$whatsapp['settings']['label'] = __( 'WhatsApp', 'gustavoo-portfolio' );
|
||||
$whatsapp['settings']['admin_field_label'] = __( 'WhatsApp', 'gustavoo-portfolio' );
|
||||
$whatsapp['settings']['mobile_keyboard_type'] = 'tel';
|
||||
$whatsapp['settings']['temp_mask'] = 'custom';
|
||||
$whatsapp['settings']['data-mask-reverse'] = 'no';
|
||||
$whatsapp['settings']['validation_rules']['required']['value'] = true;
|
||||
$fields_by_name['whatsapp'] = $whatsapp;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! isset( $fields_by_name['message'] ) || 'textarea' !== (string) ( $fields_by_name['message']['element'] ?? '' ) ) {
|
||||
$message['uniqElKey'] = 'el_gso_contact_message';
|
||||
$message['attributes']['name'] = 'message';
|
||||
$message['attributes']['placeholder'] = __( 'Descreva o motivo do contato e o que você quer construir.', 'gustavoo-portfolio' );
|
||||
$message['attributes']['rows'] = 5;
|
||||
$message['settings']['label'] = __( 'Como posso ajudar?', 'gustavoo-portfolio' );
|
||||
$message['settings']['admin_field_label'] = __( 'Motivo do contato', 'gustavoo-portfolio' );
|
||||
$message['settings']['validation_rules']['required']['value'] = true;
|
||||
$fields_by_name['message'] = $message;
|
||||
}
|
||||
|
||||
$fields = array();
|
||||
foreach ( array( 'email', 'whatsapp', 'message' ) as $required_name ) {
|
||||
if ( isset( $fields_by_name[ $required_name ] ) ) {
|
||||
$fields[] = $fields_by_name[ $required_name ];
|
||||
}
|
||||
}
|
||||
$changed = wp_json_encode( $structure['fields'] ) !== wp_json_encode( $fields );
|
||||
|
||||
if ( ! $changed ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$structure['fields'] = $fields;
|
||||
$form_fields = wp_json_encode( $structure, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES );
|
||||
|
||||
if ( class_exists( '\FluentForm\App\Services\Form\FormService' ) ) {
|
||||
( new \FluentForm\App\Services\Form\FormService() )->update(
|
||||
array(
|
||||
'form_id' => $form->id,
|
||||
'title' => $form->title,
|
||||
'status' => $form->status,
|
||||
'formFields' => $form_fields,
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$form->form_fields = $form_fields;
|
||||
$form->save();
|
||||
}
|
||||
}
|
||||
add_action( 'init', 'gustavoo_portfolio_ensure_contact_form_fields', 30 );
|
||||
|
||||
/**
|
||||
* Preserve contact fields in submissions while an older form schema is being
|
||||
* migrated. Fluent Forms only whitelists fields known by its saved schema.
|
||||
*
|
||||
* @param array $form_data Sanitized submission data.
|
||||
* @param int $form_id Submitted form ID.
|
||||
* @param array $input_configs Fluent Forms input configuration.
|
||||
* @return array
|
||||
*/
|
||||
function gustavoo_portfolio_preserve_contact_submission_fields( $form_data, $form_id, $input_configs ) {
|
||||
$contact_form_id = absint( gustavoo_portfolio_get_setting( 'contact_form_id' ) );
|
||||
|
||||
if ( ! $contact_form_id || $contact_form_id !== absint( $form_id ) ) {
|
||||
return $form_data;
|
||||
}
|
||||
|
||||
if ( isset( $_POST['whatsapp'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Fluent Forms validates the submission.
|
||||
$form_data['whatsapp'] = sanitize_text_field( wp_unslash( $_POST['whatsapp'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
}
|
||||
|
||||
if ( isset( $_POST['message'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Fluent Forms validates the submission.
|
||||
$form_data['message'] = sanitize_textarea_field( wp_unslash( $_POST['message'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
}
|
||||
|
||||
return $form_data;
|
||||
}
|
||||
add_filter( 'fluentform/insert_response_data', 'gustavoo_portfolio_preserve_contact_submission_fields', 10, 3 );
|
||||
|
||||
/**
|
||||
* Render a Fluent Forms form from a numeric, administrator-controlled ID.
|
||||
*
|
||||
* The theme deliberately never stores or executes arbitrary shortcodes. Contact
|
||||
* synchronization is configured through the FluentCRM feed inside Fluent Forms.
|
||||
*
|
||||
* @param int $form_id Form ID.
|
||||
* @param string $context Form context, used for classes and filters.
|
||||
* @return string
|
||||
*/
|
||||
function gustavoo_portfolio_render_fluent_form( $form_id, $context = 'default' ) {
|
||||
$form_id = absint( $form_id );
|
||||
$context = sanitize_html_class( $context );
|
||||
|
||||
if ( $form_id && shortcode_exists( 'fluentform' ) ) {
|
||||
if ( 'contact' === $context ) {
|
||||
gustavoo_portfolio_ensure_contact_form_fields();
|
||||
}
|
||||
|
||||
$shortcode = sprintf( '[fluentform id="%d"]', $form_id );
|
||||
$markup = do_shortcode( $shortcode );
|
||||
|
||||
// Older persisted forms may still contain only the e-mail field. Keep the
|
||||
// requested fields visible inside the real Fluent Forms <form>.
|
||||
if ( 'contact' === $context && false !== stripos( $markup, '</form>' ) ) {
|
||||
$missing_fields = '';
|
||||
|
||||
if ( false === stripos( $markup, 'name="whatsapp"' ) ) {
|
||||
$missing_fields .= '<div class="ff-el-group"><div class="ff-el-input--label"><label for="gso-contact-whatsapp">' . esc_html__( 'WhatsApp', 'gustavoo-portfolio' ) . '</label></div><div class="ff-el-input--content"><input id="gso-contact-whatsapp" name="whatsapp" class="ff-el-form-control" type="tel" placeholder="(31) 99999-9999" inputmode="tel" data-mask="(00) 00000-0000" data-mask-reverse="false"></div></div>';
|
||||
}
|
||||
|
||||
if ( false === stripos( $markup, 'name="message"' ) ) {
|
||||
$missing_fields .= '<div class="ff-el-group"><div class="ff-el-input--label"><label for="gso-contact-message">' . esc_html__( 'Como posso ajudar?', 'gustavoo-portfolio' ) . '</label></div><div class="ff-el-input--content"><textarea id="gso-contact-message" name="message" class="ff-el-form-control" rows="5" placeholder="' . esc_attr__( 'Descreva o motivo do contato e o que você quer construir.', 'gustavoo-portfolio' ) . '"></textarea></div></div>';
|
||||
}
|
||||
|
||||
if ( $missing_fields ) {
|
||||
$markup = preg_replace( '#<button\b#i', $missing_fields . '<button', $markup, 1 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter trusted Fluent Forms output before it is returned.
|
||||
*
|
||||
* @param string $markup Form markup generated by Fluent Forms.
|
||||
* @param int $form_id Form ID.
|
||||
* @param string $context Presentation context.
|
||||
*/
|
||||
return (string) apply_filters( 'gustavoo_portfolio_fluent_form_markup', $markup, $form_id, $context );
|
||||
}
|
||||
|
||||
$email = sanitize_email( (string) gustavoo_portfolio_get_setting( 'email' ) );
|
||||
$contact_url = gustavoo_portfolio_section_url( 'contato' );
|
||||
$button_url = $email ? 'mailto:' . $email : $contact_url;
|
||||
$button_label = 'newsletter' === $context
|
||||
? __( 'Quero receber novidades', 'gustavoo-portfolio' )
|
||||
: __( 'Enviar uma mensagem', 'gustavoo-portfolio' );
|
||||
|
||||
$fallback = '<div class="form-fallback form-fallback--' . esc_attr( $context ) . '">';
|
||||
$fallback .= '<p>' . esc_html__( 'O formulário está sendo configurado. Você ainda pode entrar em contato diretamente.', 'gustavoo-portfolio' ) . '</p>';
|
||||
$fallback .= '<a class="button button--primary" href="' . esc_url( $button_url ) . '">' . esc_html( $button_label ) . '</a>';
|
||||
|
||||
if ( current_user_can( 'manage_options' ) ) {
|
||||
$fallback .= '<p class="form-fallback__admin"><a href="' . esc_url( admin_url( 'edit.php?post_type=gso_project&page=gso-portfolio-settings' ) ) . '">';
|
||||
$fallback .= esc_html__( 'Configure o ID do Fluent Forms nas opções do portfólio.', 'gustavoo-portfolio' );
|
||||
$fallback .= '</a></p>';
|
||||
}
|
||||
|
||||
$fallback .= '</div>';
|
||||
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a Fluent Forms form. Output originates either from the trusted plugin
|
||||
* shortcode or from fully escaped fallback markup built above.
|
||||
*
|
||||
* @param int $form_id Form ID.
|
||||
* @param string $context Form context.
|
||||
* @return void
|
||||
*/
|
||||
function gustavoo_portfolio_the_fluent_form( $form_id, $context = 'default' ) {
|
||||
echo gustavoo_portfolio_render_fluent_form( $form_id, $context ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Trusted plugin output or escaped local fallback.
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/**
|
||||
* Theme setup and core WordPress integrations.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register theme supports, menus, and editor features.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function gustavoo_portfolio_setup() {
|
||||
load_theme_textdomain( 'gustavoo-portfolio', get_template_directory() . '/languages' );
|
||||
|
||||
add_theme_support( 'automatic-feed-links' );
|
||||
add_theme_support( 'title-tag' );
|
||||
add_theme_support( 'post-thumbnails' );
|
||||
add_theme_support( 'responsive-embeds' );
|
||||
add_theme_support( 'align-wide' );
|
||||
add_theme_support( 'editor-styles' );
|
||||
add_editor_style( 'assets/css/editor.css' );
|
||||
|
||||
add_theme_support(
|
||||
'html5',
|
||||
array(
|
||||
'comment-form',
|
||||
'comment-list',
|
||||
'gallery',
|
||||
'caption',
|
||||
'script',
|
||||
'search-form',
|
||||
'style',
|
||||
)
|
||||
);
|
||||
|
||||
add_theme_support(
|
||||
'custom-logo',
|
||||
array(
|
||||
'height' => 160,
|
||||
'width' => 160,
|
||||
'flex-height' => true,
|
||||
'flex-width' => true,
|
||||
)
|
||||
);
|
||||
|
||||
register_nav_menus(
|
||||
array(
|
||||
'primary' => __( 'Menu principal', 'gustavoo-portfolio' ),
|
||||
'news_categories' => __( 'Menu de categorias de notícias', 'gustavoo-portfolio' ),
|
||||
'footer' => __( 'Menu do rodapé', 'gustavoo-portfolio' ),
|
||||
)
|
||||
);
|
||||
|
||||
add_image_size( 'gustavoo-project-card', 720, 480, true );
|
||||
add_image_size( 'gustavoo-post-card', 720, 460, true );
|
||||
}
|
||||
add_action( 'after_setup_theme', 'gustavoo_portfolio_setup' );
|
||||
|
||||
/**
|
||||
* Define a comfortable content width for embeds and media.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function gustavoo_portfolio_content_width() {
|
||||
$GLOBALS['content_width'] = apply_filters( 'gustavoo_portfolio_content_width', 780 );
|
||||
}
|
||||
add_action( 'after_setup_theme', 'gustavoo_portfolio_content_width', 0 );
|
||||
|
||||
/**
|
||||
* Add useful state classes without coupling templates to presentation logic.
|
||||
*
|
||||
* @param string[] $classes Existing body classes.
|
||||
* @return string[]
|
||||
*/
|
||||
function gustavoo_portfolio_body_classes( $classes ) {
|
||||
if ( is_front_page() ) {
|
||||
$classes[] = 'has-overlay-header';
|
||||
}
|
||||
|
||||
if ( ! is_front_page() && is_active_sidebar( 'sidebar-blog' ) ) {
|
||||
$classes[] = 'has-blog-sidebar';
|
||||
}
|
||||
|
||||
if ( ! is_singular() ) {
|
||||
$classes[] = 'is-list-view';
|
||||
}
|
||||
|
||||
return array_unique( $classes );
|
||||
}
|
||||
add_filter( 'body_class', 'gustavoo_portfolio_body_classes' );
|
||||
|
||||
/** Set the posts page browser title to the editorial section name. */
|
||||
function gustavoo_portfolio_blog_document_title( $title ) {
|
||||
if ( is_home() ) {
|
||||
$title['title'] = __( 'Últimas notícias', 'gustavoo-portfolio' );
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
add_filter( 'document_title_parts', 'gustavoo_portfolio_blog_document_title' );
|
||||
|
||||
/** Keep the editorial top grid focused on the five newest posts. */
|
||||
function gustavoo_portfolio_news_posts_per_page( $query ) {
|
||||
if ( is_admin() || ! $query->is_main_query() || ! ( $query->is_home() || $query->is_category() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$query->set( 'posts_per_page', 5 );
|
||||
}
|
||||
add_action( 'pre_get_posts', 'gustavoo_portfolio_news_posts_per_page' );
|
||||
|
||||
/** Refresh category rewrite rules once after the editorial templates are installed. */
|
||||
function gustavoo_portfolio_refresh_editorial_rewrites() {
|
||||
$version = '1.0.0';
|
||||
|
||||
if ( $version === get_option( 'gustavoo_portfolio_editorial_rewrite_version' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
flush_rewrite_rules( false );
|
||||
update_option( 'gustavoo_portfolio_editorial_rewrite_version', $version, false );
|
||||
}
|
||||
add_action( 'init', 'gustavoo_portfolio_refresh_editorial_rewrites', 99 );
|
||||
|
||||
/**
|
||||
* Print a fallback icon only when WordPress has no configured Site Icon.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function gustavoo_portfolio_fallback_site_icon() {
|
||||
if ( has_site_icon() || ! function_exists( 'gustavoo_portfolio_get_image_source' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$icon_url = gustavoo_portfolio_get_image_source( 'logo-mark', 'png' );
|
||||
|
||||
if ( $icon_url ) {
|
||||
printf( '<link rel="icon" href="%s" sizes="192x192">' . "\n", esc_url( $icon_url ) );
|
||||
}
|
||||
}
|
||||
add_action( 'wp_head', 'gustavoo_portfolio_fallback_site_icon', 2 );
|
||||
@@ -0,0 +1,569 @@
|
||||
<?php
|
||||
/**
|
||||
* Reusable template helpers.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an image source, preferring an existing format and falling back safely.
|
||||
*
|
||||
* @param string $basename File basename without extension.
|
||||
* @param string $preferred Preferred extension.
|
||||
* @return string
|
||||
*/
|
||||
function gustavoo_portfolio_get_image_source( $basename, $preferred = 'webp' ) {
|
||||
$basename = preg_replace( '/[^a-z0-9-]/', '', strtolower( (string) $basename ) );
|
||||
$preferred = in_array( $preferred, array( 'webp', 'png' ), true ) ? $preferred : 'webp';
|
||||
$formats = array_unique( array( $preferred, 'webp', 'png' ) );
|
||||
|
||||
if ( ! $basename ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
foreach ( $formats as $format ) {
|
||||
$relative_path = '/assets/images/' . $basename . '.' . $format;
|
||||
|
||||
if ( file_exists( get_theme_file_path( $relative_path ) ) ) {
|
||||
return get_theme_file_uri( $relative_path );
|
||||
}
|
||||
}
|
||||
|
||||
// Keep a deterministic URI while assets are being provisioned by deployment.
|
||||
return get_theme_file_uri( '/assets/images/' . $basename . '.' . $preferred );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return image URLs for a picture element.
|
||||
*
|
||||
* @param string $basename File basename without extension.
|
||||
* @return array{webp:string,png:string,fallback:string}
|
||||
*/
|
||||
function gustavoo_portfolio_get_picture_sources( $basename ) {
|
||||
$webp_path = '/assets/images/' . $basename . '.webp';
|
||||
$png_path = '/assets/images/' . $basename . '.png';
|
||||
$has_webp = file_exists( get_theme_file_path( $webp_path ) );
|
||||
$has_png = file_exists( get_theme_file_path( $png_path ) );
|
||||
|
||||
return array(
|
||||
'webp' => $has_webp ? get_theme_file_uri( $webp_path ) : '',
|
||||
'png' => $has_png ? get_theme_file_uri( $png_path ) : '',
|
||||
'fallback' => $has_png ? get_theme_file_uri( $png_path ) : get_theme_file_uri( $webp_path ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the configured logo or the bundled brand mark.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function gustavoo_portfolio_the_brand() {
|
||||
$site_name = get_bloginfo( 'name' );
|
||||
|
||||
if ( has_custom_logo() ) {
|
||||
echo get_custom_logo(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Core-generated custom logo markup.
|
||||
return;
|
||||
}
|
||||
|
||||
$sources = gustavoo_portfolio_get_picture_sources( 'logo-mark' );
|
||||
?>
|
||||
<a class="custom-logo-link" href="<?php echo esc_url( home_url( '/' ) ); ?>" rel="home">
|
||||
<picture>
|
||||
<?php if ( $sources['webp'] ) : ?>
|
||||
<source srcset="<?php echo esc_url( $sources['webp'] ); ?>" type="image/webp">
|
||||
<?php endif; ?>
|
||||
<img class="custom-logo" src="<?php echo esc_url( $sources['fallback'] ); ?>" width="64" height="64" alt="<?php echo esc_attr( $site_name ); ?>">
|
||||
</picture>
|
||||
</a>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the appropriate URL for a front-page section.
|
||||
*
|
||||
* @param string $section_id Section anchor without hash.
|
||||
* @return string
|
||||
*/
|
||||
function gustavoo_portfolio_section_url( $section_id ) {
|
||||
$section_id = sanitize_html_class( $section_id );
|
||||
|
||||
if ( is_front_page() ) {
|
||||
return '#' . $section_id;
|
||||
}
|
||||
|
||||
return home_url( '/#' . $section_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured posts-page URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function gustavoo_portfolio_get_blog_url() {
|
||||
$posts_page_id = absint( get_option( 'page_for_posts' ) );
|
||||
|
||||
if ( $posts_page_id ) {
|
||||
$url = get_permalink( $posts_page_id );
|
||||
|
||||
if ( $url ) {
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
|
||||
return home_url( '/blog/' );
|
||||
}
|
||||
|
||||
/** Render the editorial category navigation used on news pages. */
|
||||
function gustavoo_portfolio_news_category_menu() {
|
||||
if ( has_nav_menu( 'news_categories' ) ) {
|
||||
?>
|
||||
<nav class="news-category-nav" aria-label="<?php esc_attr_e( 'Categorias de notícias', 'gustavoo-portfolio' ); ?>">
|
||||
<div class="news-category-nav__inner site-shell">
|
||||
<?php
|
||||
wp_nav_menu(
|
||||
array(
|
||||
'theme_location' => 'news_categories',
|
||||
'menu_class' => 'news-category-nav__list',
|
||||
'container' => false,
|
||||
'fallback_cb' => false,
|
||||
'depth' => 1,
|
||||
)
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
</nav>
|
||||
<?php
|
||||
return;
|
||||
}
|
||||
|
||||
$categories = get_categories(
|
||||
array(
|
||||
'hide_empty' => true,
|
||||
'exclude' => array( absint( get_option( 'default_category' ) ) ),
|
||||
'orderby' => 'name',
|
||||
'order' => 'ASC',
|
||||
)
|
||||
);
|
||||
|
||||
if ( empty( $categories ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$current_category_id = is_category() ? (int) get_queried_object_id() : 0;
|
||||
?>
|
||||
<nav class="news-category-nav" aria-label="<?php esc_attr_e( 'Categorias de notícias', 'gustavoo-portfolio' ); ?>">
|
||||
<div class="news-category-nav__inner site-shell">
|
||||
<a class="news-category-nav__link<?php echo is_home() ? ' is-current' : ''; ?>" href="<?php echo esc_url( gustavoo_portfolio_get_blog_url() ); ?>"><?php esc_html_e( 'Últimas notícias', 'gustavoo-portfolio' ); ?></a>
|
||||
<?php foreach ( $categories as $category ) : ?>
|
||||
<a class="news-category-nav__link<?php echo $current_category_id === (int) $category->term_id ? ' is-current' : ''; ?>" href="<?php echo esc_url( get_category_link( $category ) ); ?>"><?php echo esc_html( $category->name ); ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</nav>
|
||||
<?php
|
||||
}
|
||||
|
||||
/** Print editorial categories inside the mobile hamburger panel. */
|
||||
function gustavoo_portfolio_mobile_news_category_menu() {
|
||||
if ( has_nav_menu( 'news_categories' ) ) {
|
||||
?>
|
||||
<div class="mobile-news-categories">
|
||||
<p class="mobile-news-categories__title"><?php esc_html_e( 'Categorias', 'gustavoo-portfolio' ); ?></p>
|
||||
<?php
|
||||
wp_nav_menu(
|
||||
array(
|
||||
'theme_location' => 'news_categories',
|
||||
'menu_class' => 'mobile-news-categories__list',
|
||||
'container' => false,
|
||||
'fallback_cb' => false,
|
||||
'depth' => 1,
|
||||
)
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
return;
|
||||
}
|
||||
|
||||
$categories = get_categories(
|
||||
array(
|
||||
'hide_empty' => true,
|
||||
'exclude' => array( absint( get_option( 'default_category' ) ) ),
|
||||
'orderby' => 'name',
|
||||
'order' => 'ASC',
|
||||
)
|
||||
);
|
||||
|
||||
if ( empty( $categories ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$current_category_id = is_category() ? (int) get_queried_object_id() : 0;
|
||||
?>
|
||||
<div class="mobile-news-categories">
|
||||
<p class="mobile-news-categories__title"><?php esc_html_e( 'Categorias', 'gustavoo-portfolio' ); ?></p>
|
||||
<ul class="mobile-news-categories__list">
|
||||
<li><a class="<?php echo is_home() ? 'is-current' : ''; ?>" href="<?php echo esc_url( gustavoo_portfolio_get_blog_url() ); ?>"><?php esc_html_e( 'Últimas notícias', 'gustavoo-portfolio' ); ?></a></li>
|
||||
<?php foreach ( $categories as $category ) : ?>
|
||||
<li><a class="<?php echo $current_category_id === (int) $category->term_id ? 'is-current' : ''; ?>" href="<?php echo esc_url( get_category_link( $category ) ); ?>"><?php echo esc_html( $category->name ); ?></a></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the image used by an editorial card.
|
||||
*
|
||||
* Seeded demo posts use a local theme image until an editor assigns a
|
||||
* featured image in WordPress.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param string $size Registered image size.
|
||||
* @return string
|
||||
*/
|
||||
function gustavoo_portfolio_get_news_image_url( $post_id = 0, $size = 'gustavoo-post-card' ) {
|
||||
$post_id = $post_id ? absint( $post_id ) : get_the_ID();
|
||||
$image = get_the_post_thumbnail_url( $post_id, $size );
|
||||
|
||||
if ( $image ) {
|
||||
return $image;
|
||||
}
|
||||
|
||||
return (string) get_post_meta( $post_id, '_gso_news_image', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessible fallback menu for a site that has not assigned a WordPress menu yet.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function gustavoo_portfolio_primary_menu_fallback() {
|
||||
?>
|
||||
<ul id="primary-menu" class="primary-menu">
|
||||
<li class="menu-item"><a href="<?php echo esc_url( gustavoo_portfolio_section_url( 'sobre' ) ); ?>"><?php esc_html_e( 'Sobre', 'gustavoo-portfolio' ); ?></a></li>
|
||||
<li class="menu-item"><a href="<?php echo esc_url( gustavoo_portfolio_section_url( 'servicos' ) ); ?>"><?php esc_html_e( 'Serviços', 'gustavoo-portfolio' ); ?></a></li>
|
||||
<li class="menu-item"><a href="<?php echo esc_url( gustavoo_portfolio_section_url( 'projetos' ) ); ?>"><?php esc_html_e( 'Projetos', 'gustavoo-portfolio' ); ?></a></li>
|
||||
<li class="menu-item"><a href="<?php echo esc_url( gustavoo_portfolio_get_blog_url() ); ?>"><?php esc_html_e( 'Blog', 'gustavoo-portfolio' ); ?></a></li>
|
||||
<li class="menu-item"><a href="<?php echo esc_url( gustavoo_portfolio_section_url( 'contato' ) ); ?>"><?php esc_html_e( 'Contato', 'gustavoo-portfolio' ); ?></a></li>
|
||||
</ul>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Return supported social profile links.
|
||||
*
|
||||
* @return array<string, array{label:string,url:string}>
|
||||
*/
|
||||
function gustavoo_portfolio_get_social_links() {
|
||||
$profiles = array(
|
||||
'linkedin' => array(
|
||||
'label' => __( 'LinkedIn', 'gustavoo-portfolio' ),
|
||||
'url' => (string) gustavoo_portfolio_get_setting( 'linkedin_url' ),
|
||||
),
|
||||
'instagram' => array(
|
||||
'label' => __( 'Instagram', 'gustavoo-portfolio' ),
|
||||
'url' => (string) gustavoo_portfolio_get_setting( 'instagram_url' ),
|
||||
),
|
||||
);
|
||||
|
||||
return array_filter(
|
||||
$profiles,
|
||||
static function ( $profile ) {
|
||||
return ! empty( $profile['url'] );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print social links with explicit new-window text for assistive technology.
|
||||
*
|
||||
* @param string $class Optional list class.
|
||||
* @return void
|
||||
*/
|
||||
function gustavoo_portfolio_social_links( $class = 'social-links' ) {
|
||||
$profiles = gustavoo_portfolio_get_social_links();
|
||||
|
||||
if ( empty( $profiles ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
printf( '<ul class="%s">', esc_attr( $class ) );
|
||||
|
||||
foreach ( $profiles as $slug => $profile ) {
|
||||
printf(
|
||||
'<li class="social-links__item social-links__item--%1$s"><a href="%2$s" target="_blank" rel="noopener noreferrer external"><span>%3$s</span><span class="screen-reader-text"> %4$s</span></a></li>',
|
||||
esc_attr( $slug ),
|
||||
esc_url( $profile['url'] ),
|
||||
esc_html( $profile['label'] ),
|
||||
esc_html__( '(abre em uma nova guia)', 'gustavoo-portfolio' )
|
||||
);
|
||||
}
|
||||
|
||||
echo '</ul>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Print share links for a blog post using inline brand SVG icons.
|
||||
*
|
||||
* @param string $class Optional wrapper class.
|
||||
* @return void
|
||||
*/
|
||||
function gustavoo_portfolio_post_share_links( $class = 'entry-share' ) {
|
||||
$share_url = rawurlencode( get_permalink() );
|
||||
$share_title = rawurlencode( get_the_title() );
|
||||
$links = array(
|
||||
'WhatsApp' => array(
|
||||
'url' => 'https://api.whatsapp.com/send?text=' . $share_title . '%20' . $share_url,
|
||||
'icon' => '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M20.5 3.5A11.8 11.8 0 0 0 12.1 0C5.6 0 .3 5.3.3 11.8c0 2.1.5 4.1 1.6 5.9L.2 23.8l6.3-1.7a11.8 11.8 0 0 0 5.6 1.4h.1c6.5 0 11.8-5.3 11.8-11.8 0-3.1-1.2-6-3.5-8.2Zm-8.4 17.9c-1.8 0-3.5-.5-5-1.4l-.4-.2-3.7 1 1-3.6-.2-.4a9.6 9.6 0 1 1 8.3 4.6Zm5.3-7.2c-.3-.2-1.7-.8-2-.9-.3-.1-.5-.2-.7.2-.2.3-.8.9-.9 1.1-.2.2-.3.2-.6.1-1.7-.8-2.8-1.5-3.9-3.4-.3-.5.3-.5.8-1.7.1-.2 0-.4 0-.6l-.9-2.1c-.2-.6-.5-.5-.7-.5h-.6c-.2 0-.5.1-.8.4-.3.3-1 1-1 2.4s1 2.8 1.1 3c.1.2 2 3.1 4.9 4.4 1.8.8 2.5.9 3.4.8.5-.1 1.7-.7 1.9-1.4.2-.7.2-1.3.1-1.4-.1-.2-.3-.3-.6-.4Z" fill="currentColor"/></svg>',
|
||||
),
|
||||
'Facebook' => array(
|
||||
'url' => 'https://www.facebook.com/sharer/sharer.php?u=' . $share_url,
|
||||
'icon' => '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M13.5 22v-8h2.7l.4-3h-3.1V9.1c0-.9.3-1.5 1.6-1.5h1.7V4.9c-.3 0-1.3-.1-2.5-.1-2.5 0-4.2 1.5-4.2 4.3V11H7.3v3h2.8v8h3.4Z" fill="currentColor"/></svg>',
|
||||
),
|
||||
'X' => array(
|
||||
'url' => 'https://twitter.com/intent/tweet?text=' . $share_title . '&url=' . $share_url,
|
||||
'icon' => '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M18.9 2h3.7l-8.1 9.3L24 22h-7.4l-5.8-7.6L4.2 22H.5l8.4-9.7L0 2h7.6l5.2 6.9L18.9 2Zm-1.3 17.9h2.1L6.4 4H4.1l13.5 15.9Z" fill="currentColor"/></svg>',
|
||||
),
|
||||
'LinkedIn' => array(
|
||||
'url' => 'https://www.linkedin.com/sharing/share-offsite/?url=' . $share_url,
|
||||
'icon' => '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M5.2 3.4A2.2 2.2 0 1 1 .8 3.4a2.2 2.2 0 0 1 4.4 0ZM1.1 7h4.2v13.5H1.1V7Zm6.8 0h4v1.8h.1c.6-1.1 2-2.2 4.1-2.2 4.4 0 5.2 2.9 5.2 6.7v7.2h-4.2v-6.4c0-1.5 0-3.5-2.1-3.5s-2.4 1.7-2.4 3.4v6.5H7.9V7Z" fill="currentColor"/></svg>',
|
||||
),
|
||||
);
|
||||
|
||||
printf( '<div class="%1$s" aria-label="%2$s">', esc_attr( $class ), esc_attr__( 'Compartilhar postagem', 'gustavoo-portfolio' ) );
|
||||
|
||||
foreach ( $links as $label => $link ) {
|
||||
printf(
|
||||
'<a href="%1$s" target="_blank" rel="noopener noreferrer" aria-label="%2$s">%3$s<span class="screen-reader-text">%2$s</span></a>',
|
||||
esc_url( $link['url'] ),
|
||||
esc_attr( $label ),
|
||||
$link['icon'] // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static SVG markup.
|
||||
);
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a configured phone number to a WhatsApp URL.
|
||||
*
|
||||
* @param string $phone Raw phone number.
|
||||
* @param string $message Optional pre-filled conversation message.
|
||||
* @return string
|
||||
*/
|
||||
function gustavoo_portfolio_get_whatsapp_url( $phone, $message = '' ) {
|
||||
$digits = preg_replace( '/\D+/', '', (string) $phone );
|
||||
|
||||
if ( ! $digits ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( in_array( strlen( $digits ), array( 10, 11 ), true ) ) {
|
||||
$digits = '55' . $digits;
|
||||
}
|
||||
|
||||
$url = 'https://wa.me/' . $digits;
|
||||
$message = trim( wp_strip_all_tags( (string) $message ) );
|
||||
|
||||
if ( '' !== $message ) {
|
||||
$url .= '?text=' . rawurlencode( $message );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/** Print the configured floating WhatsApp contact link. */
|
||||
function gustavoo_portfolio_the_whatsapp_float() {
|
||||
$phone = (string) gustavoo_portfolio_get_setting( 'whatsapp' );
|
||||
$message = (string) gustavoo_portfolio_get_setting( 'whatsapp_message' );
|
||||
$url = gustavoo_portfolio_get_whatsapp_url( $phone, $message );
|
||||
|
||||
if ( ! $url ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<div class="whatsapp-float-wrap"<?php echo is_customize_preview() ? ' data-customize-partial-id="gustavoo_portfolio_whatsapp_shortcut"' : ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static attribute. ?>>
|
||||
<a class="whatsapp-float" href="<?php echo esc_url( $url ); ?>" target="_blank" rel="noopener noreferrer external" aria-label="<?php esc_attr_e( 'Conversar pelo WhatsApp (abre em uma nova guia)', 'gustavoo-portfolio' ); ?>">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path fill="currentColor" d="M20.5 3.5A11.8 11.8 0 0 0 12.1 0C5.6 0 .3 5.3.3 11.8c0 2.1.5 4.1 1.6 5.9L.2 23.8l6.3-1.7a11.8 11.8 0 0 0 5.6 1.4h.1c6.5 0 11.8-5.3 11.8-11.8 0-3.1-1.2-6-3.5-8.2ZM12.1 21.4c-1.8 0-3.5-.5-5-1.4l-.4-.2-3.7 1 1-3.6-.2-.4a9.6 9.6 0 1 1 8.3 4.6Zm5.3-7.2c-.3-.2-1.7-.8-2-.9-.3-.1-.5-.2-.7.2-.2.3-.8.9-.9 1.1-.2.2-.3.2-.6.1-1.7-.8-2.8-1.5-3.9-3.4-.3-.5.3-.5.8-1.7.1-.2 0-.4 0-.6l-.9-2.1c-.2-.6-.5-.5-.7-.5h-.6c-.2 0-.5.1-.8.4-.3.3-1 1-1 2.4s1 2.8 1.1 3c.1.2 2 3.1 4.9 4.4 1.8.8 2.5.9 3.4.8.5-.1 1.7-.7 1.9-1.4.2-.7.2-1.3.1-1.4-.1-.2-.3-.3-.6-.4Z"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an external project URL without making a network request.
|
||||
*
|
||||
* @param string $url Candidate URL.
|
||||
* @return string Empty string when invalid.
|
||||
*/
|
||||
function gustavoo_portfolio_validate_project_url( $url ) {
|
||||
$url = esc_url_raw( (string) $url, array( 'http', 'https' ) );
|
||||
$scheme = wp_parse_url( $url, PHP_URL_SCHEME );
|
||||
$host = wp_parse_url( $url, PHP_URL_HOST );
|
||||
|
||||
if ( ! $url || ! $host || ! in_array( strtolower( (string) $scheme ), array( 'http', 'https' ), true ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return featured projects first, then fill the requested limit by menu order.
|
||||
*
|
||||
* @param int $limit Maximum number of projects.
|
||||
* @return WP_Post[]
|
||||
*/
|
||||
function gustavoo_portfolio_get_home_projects( $limit = 6 ) {
|
||||
$limit = max( 1, min( 24, absint( $limit ) ) );
|
||||
|
||||
if ( ! post_type_exists( 'gso_project' ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$base_args = array(
|
||||
'post_type' => 'gso_project',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $limit * 3,
|
||||
'orderby' => array(
|
||||
'menu_order' => 'ASC',
|
||||
'date' => 'DESC',
|
||||
),
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => true,
|
||||
'update_post_term_cache' => true,
|
||||
);
|
||||
|
||||
$featured_args = $base_args;
|
||||
$featured_args['meta_query'] = array(
|
||||
array(
|
||||
'key' => '_gso_project_featured',
|
||||
'value' => array( '1', 'yes', 'on', 'true' ),
|
||||
'compare' => 'IN',
|
||||
),
|
||||
);
|
||||
|
||||
$featured = get_posts( $featured_args );
|
||||
$others = get_posts( $base_args );
|
||||
$projects = array();
|
||||
$seen = array();
|
||||
|
||||
foreach ( array_merge( $featured, $others ) as $project ) {
|
||||
if ( isset( $seen[ $project->ID ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url = gustavoo_portfolio_validate_project_url( get_post_meta( $project->ID, '_gso_project_url', true ) );
|
||||
|
||||
if ( ! $url ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$seen[ $project->ID ] = true;
|
||||
$projects[] = $project;
|
||||
|
||||
if ( count( $projects ) >= $limit ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $projects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print post publication metadata.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function gustavoo_portfolio_posted_on() {
|
||||
$published = sprintf(
|
||||
'<time class="entry-date published" datetime="%1$s">%2$s</time>',
|
||||
esc_attr( get_the_date( DATE_W3C ) ),
|
||||
esc_html( get_the_date() )
|
||||
);
|
||||
|
||||
printf(
|
||||
'<span class="posted-on"><span class="screen-reader-text">%1$s </span>%2$s</span>',
|
||||
esc_html__( 'Publicado em', 'gustavoo-portfolio' ),
|
||||
$published // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped immediately above.
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the post author link.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function gustavoo_portfolio_posted_by() {
|
||||
printf(
|
||||
'<span class="byline"><span class="screen-reader-text">%1$s </span><a class="url fn n" href="%2$s">%3$s</a></span>',
|
||||
esc_html__( 'Por', 'gustavoo-portfolio' ),
|
||||
esc_url( get_author_posts_url( (int) get_the_author_meta( 'ID' ) ) ),
|
||||
esc_html( get_the_author() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate reading time from the current post content.
|
||||
*
|
||||
* @param int|null $post_id Optional post ID.
|
||||
* @return string
|
||||
*/
|
||||
function gustavoo_portfolio_reading_time( $post_id = null ) {
|
||||
$post_id = $post_id ? absint( $post_id ) : get_the_ID();
|
||||
$content = wp_strip_all_tags( strip_shortcodes( (string) get_post_field( 'post_content', $post_id ) ) );
|
||||
$words = preg_split( '/\s+/u', trim( $content ), -1, PREG_SPLIT_NO_EMPTY );
|
||||
$minutes = max( 1, (int) ceil( count( is_array( $words ) ? $words : array() ) / 220 ) );
|
||||
|
||||
return sprintf(
|
||||
/* translators: %d: reading time in minutes. */
|
||||
_n( '%d min de leitura', '%d min de leitura', $minutes, 'gustavoo-portfolio' ),
|
||||
$minutes
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print category and tag links for a post.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function gustavoo_portfolio_entry_terms() {
|
||||
$categories = get_the_category_list( esc_html_x( ', ', 'category list separator', 'gustavoo-portfolio' ) );
|
||||
$tags = get_the_tag_list( '', esc_html_x( ', ', 'tag list separator', 'gustavoo-portfolio' ) );
|
||||
|
||||
if ( $categories ) {
|
||||
printf(
|
||||
'<div class="entry-terms entry-terms--categories"><span>%1$s</span> %2$s</div>',
|
||||
esc_html__( 'Categorias:', 'gustavoo-portfolio' ),
|
||||
wp_kses_post( $categories )
|
||||
);
|
||||
}
|
||||
|
||||
if ( $tags ) {
|
||||
printf(
|
||||
'<div class="entry-terms entry-terms--tags"><span>%1$s</span> %2$s</div>',
|
||||
esc_html__( 'Tags:', 'gustavoo-portfolio' ),
|
||||
wp_kses_post( $tags )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print main-query pagination with accessible labels.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function gustavoo_portfolio_pagination() {
|
||||
the_posts_pagination(
|
||||
array(
|
||||
'mid_size' => 1,
|
||||
'prev_text' => sprintf( '<span aria-hidden="true">←</span> %s', esc_html__( 'Anteriores', 'gustavoo-portfolio' ) ),
|
||||
'next_text' => sprintf( '%s <span aria-hidden="true">→</span>', esc_html__( 'Próximos', 'gustavoo-portfolio' ) ),
|
||||
'screen_reader_text' => __( 'Navegação entre páginas', 'gustavoo-portfolio' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
/**
|
||||
* Widget areas and the reusable newsletter widget.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register sidebars used by blog and footer templates.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function gustavoo_portfolio_register_sidebars() {
|
||||
$shared = array(
|
||||
'before_widget' => '<section id="%1$s" class="widget %2$s">',
|
||||
'after_widget' => '</section>',
|
||||
'before_title' => '<h2 class="widget__title">',
|
||||
'after_title' => '</h2>',
|
||||
);
|
||||
|
||||
register_sidebar(
|
||||
array_merge(
|
||||
$shared,
|
||||
array(
|
||||
'name' => __( 'Barra lateral do blog', 'gustavoo-portfolio' ),
|
||||
'id' => 'sidebar-blog',
|
||||
'description' => __( 'Widgets exibidos ao lado de posts, arquivos e buscas.', 'gustavoo-portfolio' ),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
register_sidebar(
|
||||
array_merge(
|
||||
$shared,
|
||||
array(
|
||||
'name' => __( 'Rodapé — coluna 1', 'gustavoo-portfolio' ),
|
||||
'id' => 'footer-1',
|
||||
'description' => __( 'Primeira coluna de widgets do rodapé.', 'gustavoo-portfolio' ),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
register_sidebar(
|
||||
array_merge(
|
||||
$shared,
|
||||
array(
|
||||
'name' => __( 'Rodapé — coluna 2', 'gustavoo-portfolio' ),
|
||||
'id' => 'footer-2',
|
||||
'description' => __( 'Segunda coluna de widgets do rodapé.', 'gustavoo-portfolio' ),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
add_action( 'widgets_init', 'gustavoo_portfolio_register_sidebars' );
|
||||
|
||||
/**
|
||||
* Newsletter widget backed by the same safe Fluent Forms renderer as the CTA.
|
||||
*/
|
||||
class Gustavao_Portfolio_Newsletter_Widget extends WP_Widget {
|
||||
/**
|
||||
* Register the widget with WordPress.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'gustavoo_portfolio_newsletter',
|
||||
__( 'Gustavo — Newsletter', 'gustavoo-portfolio' ),
|
||||
array(
|
||||
'classname' => 'widget_newsletter',
|
||||
'description' => __( 'Exibe a chamada de newsletter integrada ao Fluent Forms e FluentCRM.', 'gustavoo-portfolio' ),
|
||||
'customize_selective_refresh' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render widget front end.
|
||||
*
|
||||
* @param array<string, string> $args Sidebar wrappers.
|
||||
* @param array<string, mixed> $instance Saved instance.
|
||||
* @return void
|
||||
*/
|
||||
public function widget( $args, $instance ) {
|
||||
$title = ! empty( $instance['title'] ) ? $instance['title'] : gustavoo_portfolio_get_setting( 'newsletter_heading' );
|
||||
$text = ! empty( $instance['text'] ) ? $instance['text'] : gustavoo_portfolio_get_setting( 'newsletter_text' );
|
||||
$form_id = ! empty( $instance['form_id'] ) ? absint( $instance['form_id'] ) : absint( gustavoo_portfolio_get_setting( 'newsletter_form_id' ) );
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Registered sidebar wrapper.
|
||||
|
||||
if ( $title ) {
|
||||
echo $args['before_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Registered sidebar wrapper.
|
||||
echo esc_html( $title );
|
||||
echo $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Registered sidebar wrapper.
|
||||
}
|
||||
|
||||
if ( $text ) {
|
||||
printf( '<p class="widget_newsletter__text">%s</p>', esc_html( $text ) );
|
||||
}
|
||||
|
||||
gustavoo_portfolio_the_fluent_form( $form_id, 'newsletter' );
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Registered sidebar wrapper.
|
||||
}
|
||||
|
||||
/**
|
||||
* Render widget controls.
|
||||
*
|
||||
* @param array<string, mixed> $instance Saved instance.
|
||||
* @return void
|
||||
*/
|
||||
public function form( $instance ) {
|
||||
$title = isset( $instance['title'] ) ? (string) $instance['title'] : '';
|
||||
$text = isset( $instance['text'] ) ? (string) $instance['text'] : '';
|
||||
$form_id = isset( $instance['form_id'] ) ? absint( $instance['form_id'] ) : 0;
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Título:', 'gustavoo-portfolio' ); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>"><?php esc_html_e( 'Texto:', 'gustavoo-portfolio' ); ?></label>
|
||||
<textarea class="widefat" rows="4" id="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'text' ) ); ?>"><?php echo esc_textarea( $text ); ?></textarea>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr( $this->get_field_id( 'form_id' ) ); ?>"><?php esc_html_e( 'ID do Fluent Forms (vazio usa o global):', 'gustavoo-portfolio' ); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr( $this->get_field_id( 'form_id' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'form_id' ) ); ?>" type="number" min="0" step="1" value="<?php echo esc_attr( $form_id ); ?>">
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize widget settings.
|
||||
*
|
||||
* @param array<string, mixed> $new_instance New values.
|
||||
* @param array<string, mixed> $old_instance Previous values.
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function update( $new_instance, $old_instance ) {
|
||||
unset( $old_instance );
|
||||
|
||||
return array(
|
||||
'title' => isset( $new_instance['title'] ) ? sanitize_text_field( $new_instance['title'] ) : '',
|
||||
'text' => isset( $new_instance['text'] ) ? sanitize_textarea_field( $new_instance['text'] ) : '',
|
||||
'form_id' => isset( $new_instance['form_id'] ) ? absint( $new_instance['form_id'] ) : 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the newsletter widget after core has loaded WP_Widget.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function gustavoo_portfolio_register_widgets() {
|
||||
register_widget( 'Gustavao_Portfolio_Newsletter_Widget' );
|
||||
}
|
||||
add_action( 'widgets_init', 'gustavoo_portfolio_register_widgets' );
|
||||
|
||||
/**
|
||||
* Add one newsletter widget to the blog sidebar on first theme activation.
|
||||
*
|
||||
* Existing widget arrangements are preserved and the operation is idempotent.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function gustavoo_portfolio_seed_newsletter_widget() {
|
||||
$widget_id_base = 'gustavoo_portfolio_newsletter';
|
||||
$instances = get_option( 'widget_' . $widget_id_base, array() );
|
||||
$instances = is_array( $instances ) ? $instances : array();
|
||||
$sidebars = wp_get_sidebars_widgets();
|
||||
$sidebars = is_array( $sidebars ) ? $sidebars : array();
|
||||
|
||||
foreach ( $sidebars as $widgets ) {
|
||||
foreach ( (array) $widgets as $widget_id ) {
|
||||
if ( str_starts_with( (string) $widget_id, $widget_id_base . '-' ) ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$indexes = array_filter( array_keys( $instances ), 'is_int' );
|
||||
$index = $indexes ? max( $indexes ) + 1 : 1;
|
||||
|
||||
$instances[ $index ] = array(
|
||||
'title' => __( 'Newsletter', 'gustavoo-portfolio' ),
|
||||
'text' => __( 'Receba novos artigos sobre desenvolvimento, infraestrutura e automação.', 'gustavoo-portfolio' ),
|
||||
'form_id' => 0,
|
||||
);
|
||||
$instances['_multiwidget'] = 1;
|
||||
|
||||
$sidebars['sidebar-blog'] = array_values(
|
||||
array_merge(
|
||||
array( $widget_id_base . '-' . $index ),
|
||||
isset( $sidebars['sidebar-blog'] ) ? (array) $sidebars['sidebar-blog'] : array()
|
||||
)
|
||||
);
|
||||
|
||||
update_option( 'widget_' . $widget_id_base, $instances, false );
|
||||
update_option( 'sidebars_widgets', $sidebars, false );
|
||||
}
|
||||
add_action( 'after_switch_theme', 'gustavoo_portfolio_seed_newsletter_widget' );
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* Main fallback template.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
get_header();
|
||||
?>
|
||||
|
||||
<main id="primary" class="site-main content-index">
|
||||
<header class="page-hero">
|
||||
<div class="site-shell page-hero__inner">
|
||||
<p class="eyebrow"><?php esc_html_e( 'Conteúdo', 'gustavoo-portfolio' ); ?></p>
|
||||
<h1 class="page-hero__title"><?php esc_html_e( 'Publicações recentes', 'gustavoo-portfolio' ); ?></h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="site-shell article-layout">
|
||||
<div class="article-layout__main">
|
||||
<?php if ( have_posts() ) : ?>
|
||||
<div class="posts-grid">
|
||||
<?php
|
||||
while ( have_posts() ) {
|
||||
the_post();
|
||||
get_template_part( 'template-parts/global/post-card' );
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php gustavoo_portfolio_pagination(); ?>
|
||||
<?php else : ?>
|
||||
<?php get_template_part( 'template-parts/content/content', 'none' ); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php get_sidebar(); ?>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php
|
||||
get_footer();
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* Standard page template.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
get_header();
|
||||
?>
|
||||
|
||||
<main id="primary" class="site-main page-main">
|
||||
<div class="site-shell content-narrow">
|
||||
<?php
|
||||
while ( have_posts() ) {
|
||||
the_post();
|
||||
get_template_part( 'template-parts/content/content', 'page' );
|
||||
|
||||
if ( comments_open() || get_comments_number() ) {
|
||||
comments_template();
|
||||
}
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php
|
||||
get_footer();
|
||||
|
After Width: | Height: | Size: 950 KiB |
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* Search results.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
get_header();
|
||||
$gustavoo_search_query = get_search_query();
|
||||
?>
|
||||
|
||||
<main id="primary" class="site-main search-main">
|
||||
<header class="page-hero page-hero--search">
|
||||
<div class="site-shell page-hero__inner">
|
||||
<p class="eyebrow"><?php esc_html_e( 'Pesquisa', 'gustavoo-portfolio' ); ?></p>
|
||||
<h1 class="page-hero__title">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: search query. */
|
||||
esc_html__( 'Resultados para “%s”', 'gustavoo-portfolio' ),
|
||||
esc_html( $gustavoo_search_query )
|
||||
);
|
||||
?>
|
||||
</h1>
|
||||
<div class="page-hero__search"><?php get_search_form(); ?></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="site-shell article-layout">
|
||||
<div class="article-layout__main">
|
||||
<?php if ( have_posts() ) : ?>
|
||||
<p class="results-count">
|
||||
<?php
|
||||
global $wp_query;
|
||||
printf(
|
||||
/* translators: %s: number of search results. */
|
||||
esc_html( _n( '%s resultado encontrado', '%s resultados encontrados', (int) $wp_query->found_posts, 'gustavoo-portfolio' ) ),
|
||||
esc_html( number_format_i18n( (int) $wp_query->found_posts ) )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<div class="posts-grid">
|
||||
<?php
|
||||
while ( have_posts() ) {
|
||||
the_post();
|
||||
get_template_part( 'template-parts/global/post-card' );
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php gustavoo_portfolio_pagination(); ?>
|
||||
<?php else : ?>
|
||||
<?php get_template_part( 'template-parts/content/content', 'none' ); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php get_sidebar(); ?>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php
|
||||
get_footer();
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
/**
|
||||
* Accessible search form.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$gustavoo_search_id = wp_unique_id( 'site-search-' );
|
||||
?>
|
||||
<form role="search" method="get" class="search-form" action="<?php echo esc_url( home_url( '/' ) ); ?>">
|
||||
<label class="search-form__label" for="<?php echo esc_attr( $gustavoo_search_id ); ?>">
|
||||
<span class="screen-reader-text"><?php echo esc_html_x( 'Pesquisar por:', 'label', 'gustavoo-portfolio' ); ?></span>
|
||||
<input id="<?php echo esc_attr( $gustavoo_search_id ); ?>" type="search" class="search-form__field" placeholder="<?php echo esc_attr_x( 'Pesquisar no blog…', 'placeholder', 'gustavoo-portfolio' ); ?>" value="<?php echo esc_attr( get_search_query() ); ?>" name="s">
|
||||
</label>
|
||||
<button type="submit" class="search-form__submit">
|
||||
<span><?php echo esc_html_x( 'Pesquisar', 'submit button', 'gustavoo-portfolio' ); ?></span>
|
||||
<span aria-hidden="true">→</span>
|
||||
</button>
|
||||
</form>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* Blog sidebar.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<aside id="secondary" class="sidebar" aria-label="<?php esc_attr_e( 'Barra lateral do blog', 'gustavoo-portfolio' ); ?>">
|
||||
<?php if ( is_active_sidebar( 'sidebar-blog' ) ) : ?>
|
||||
<?php dynamic_sidebar( 'sidebar-blog' ); ?>
|
||||
<?php else : ?>
|
||||
<section class="widget widget_search">
|
||||
<h2 class="widget__title"><?php esc_html_e( 'Pesquisar', 'gustavoo-portfolio' ); ?></h2>
|
||||
<?php get_search_form(); ?>
|
||||
</section>
|
||||
|
||||
<section class="widget widget_categories">
|
||||
<h2 class="widget__title"><?php esc_html_e( 'Categorias', 'gustavoo-portfolio' ); ?></h2>
|
||||
<ul>
|
||||
<?php
|
||||
wp_list_categories(
|
||||
array(
|
||||
'title_li' => '',
|
||||
'show_count' => true,
|
||||
)
|
||||
);
|
||||
?>
|
||||
</ul>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
</aside>
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
/**
|
||||
* Single blog post template.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
get_header();
|
||||
|
||||
while ( have_posts() ) :
|
||||
the_post();
|
||||
?>
|
||||
<main id="primary" class="site-main single-post-main">
|
||||
<header class="entry-hero">
|
||||
<div class="site-shell entry-hero__inner">
|
||||
<nav class="entry-breadcrumbs" aria-label="<?php esc_attr_e( 'Navegação estrutural', 'gustavoo-portfolio' ); ?>">
|
||||
<a href="<?php echo esc_url( home_url( '/' ) ); ?>"><?php esc_html_e( 'Início', 'gustavoo-portfolio' ); ?></a>
|
||||
<span aria-hidden="true">›</span>
|
||||
<a href="<?php echo esc_url( gustavoo_portfolio_get_blog_url() ); ?>"><?php esc_html_e( 'Últimas notícias', 'gustavoo-portfolio' ); ?></a>
|
||||
<?php $gustavoo_breadcrumb_categories = get_the_category(); ?>
|
||||
<?php if ( ! empty( $gustavoo_breadcrumb_categories ) ) : ?>
|
||||
<span aria-hidden="true">›</span>
|
||||
<a href="<?php echo esc_url( get_category_link( $gustavoo_breadcrumb_categories[0] ) ); ?>"><?php echo esc_html( $gustavoo_breadcrumb_categories[0]->name ); ?></a>
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
<?php $gustavoo_categories = get_the_category(); ?>
|
||||
<?php if ( ! empty( $gustavoo_categories ) ) : ?>
|
||||
<a class="entry-category-badge" href="<?php echo esc_url( get_category_link( $gustavoo_categories[0] ) ); ?>"><?php echo esc_html( $gustavoo_categories[0]->name ); ?></a>
|
||||
<?php endif; ?>
|
||||
<?php the_title( '<h1 class="entry-hero__title">', '</h1>' ); ?>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="site-shell entry-post-meta">
|
||||
<div class="entry-post-meta__author">
|
||||
<strong><?php echo esc_html( get_the_author() ); ?></strong>
|
||||
<span aria-hidden="true">·</span>
|
||||
<span><?php esc_html_e( 'Publicado em', 'gustavoo-portfolio' ); ?> <?php echo esc_html( get_the_date() ); ?></span>
|
||||
<span aria-hidden="true">·</span>
|
||||
<span><?php echo esc_html( gustavoo_portfolio_reading_time() ); ?></span>
|
||||
</div>
|
||||
<?php gustavoo_portfolio_post_share_links(); ?>
|
||||
</div>
|
||||
|
||||
<?php if ( has_post_thumbnail() ) : ?>
|
||||
<figure class="entry-featured-image">
|
||||
<?php the_post_thumbnail( 'full', array( 'class' => 'entry-featured-image__image' ) ); ?>
|
||||
</figure>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="site-shell article-layout article-layout--single">
|
||||
<article id="post-<?php the_ID(); ?>" <?php post_class( 'article article--single' ); ?>>
|
||||
<div class="entry-content prose">
|
||||
<?php the_content(); ?>
|
||||
<?php
|
||||
wp_link_pages(
|
||||
array(
|
||||
'before' => '<nav class="page-links" aria-label="' . esc_attr__( 'Páginas deste artigo', 'gustavoo-portfolio' ) . '"><span>' . esc_html__( 'Páginas:', 'gustavoo-portfolio' ) . '</span>',
|
||||
'after' => '</nav>',
|
||||
)
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
|
||||
<footer class="entry-footer">
|
||||
<?php gustavoo_portfolio_entry_terms(); ?>
|
||||
</footer>
|
||||
|
||||
<?php
|
||||
$gustavoo_categories = get_the_category();
|
||||
?>
|
||||
|
||||
<section class="post-share-panel" aria-labelledby="post-share-title">
|
||||
<h2 id="post-share-title" class="post-share-panel__title"><?php esc_html_e( 'Compartilhar:', 'gustavoo-portfolio' ); ?></h2>
|
||||
<?php gustavoo_portfolio_post_share_links( 'post-share-panel__links' ); ?>
|
||||
</section>
|
||||
|
||||
<?php if ( ! empty( $gustavoo_categories ) ) : ?>
|
||||
<?php
|
||||
$gustavoo_related_posts = new WP_Query(
|
||||
array(
|
||||
'category__in' => wp_list_pluck( $gustavoo_categories, 'term_id' ),
|
||||
'post__not_in' => array( get_the_ID() ),
|
||||
'posts_per_page' => 3,
|
||||
'ignore_sticky_posts' => true,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
)
|
||||
);
|
||||
?>
|
||||
<?php if ( $gustavoo_related_posts->have_posts() ) : ?>
|
||||
<section class="related-posts" aria-labelledby="related-posts-title">
|
||||
<div class="related-posts__heading"><span></span><h2 id="related-posts-title"><?php esc_html_e( 'Relacionadas', 'gustavoo-portfolio' ); ?></h2><span></span></div>
|
||||
<div class="related-posts__grid">
|
||||
<?php while ( $gustavoo_related_posts->have_posts() ) : $gustavoo_related_posts->the_post(); ?>
|
||||
<article <?php post_class( 'related-post' ); ?>>
|
||||
<a class="related-post__link" href="<?php the_permalink(); ?>">
|
||||
<?php if ( has_post_thumbnail() ) : ?>
|
||||
<?php the_post_thumbnail( 'gustavoo-post-card', array( 'class' => 'related-post__image', 'loading' => 'lazy', 'decoding' => 'async' ) ); ?>
|
||||
<?php endif; ?>
|
||||
<h3 class="related-post__title"><?php the_title(); ?></h3>
|
||||
</a>
|
||||
</article>
|
||||
<?php endwhile; ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
<?php wp_reset_postdata(); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php $gustavoo_previous_post = get_previous_post(); ?>
|
||||
<?php $gustavoo_next_post = get_next_post(); ?>
|
||||
<?php if ( $gustavoo_previous_post || $gustavoo_next_post ) : ?>
|
||||
<nav class="post-navigation post-navigation--editorial" aria-label="<?php esc_attr_e( 'Navegação entre artigos', 'gustavoo-portfolio' ); ?>">
|
||||
<div class="nav-links">
|
||||
<?php if ( $gustavoo_previous_post ) : ?>
|
||||
<a class="nav-previous" href="<?php echo esc_url( get_permalink( $gustavoo_previous_post ) ); ?>"><span class="nav-subtitle">← <?php esc_html_e( 'Anterior', 'gustavoo-portfolio' ); ?></span><span class="nav-title"><?php echo esc_html( get_the_title( $gustavoo_previous_post ) ); ?></span></a>
|
||||
<?php endif; ?>
|
||||
<?php if ( $gustavoo_next_post ) : ?>
|
||||
<a class="nav-next" href="<?php echo esc_url( get_permalink( $gustavoo_next_post ) ); ?>"><span class="nav-subtitle"><?php esc_html_e( 'Próximo', 'gustavoo-portfolio' ); ?> →</span><span class="nav-title"><?php echo esc_html( get_the_title( $gustavoo_next_post ) ); ?></span></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( comments_open() || get_comments_number() ) {
|
||||
comments_template();
|
||||
}
|
||||
?>
|
||||
</article>
|
||||
|
||||
<?php get_sidebar(); ?>
|
||||
</div>
|
||||
</main>
|
||||
<?php
|
||||
endwhile;
|
||||
|
||||
get_footer();
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
Theme Name: Gustavo Portfolio
|
||||
Theme URI: https://gustavoo.me/
|
||||
Author: Gustavo Oliveira
|
||||
Author URI: https://gustavoo.me/
|
||||
Description: Tema clássico para o portfólio e blog de Gustavo Oliveira.
|
||||
Version: 1.1.0
|
||||
Requires at least: 6.5
|
||||
Requires PHP: 8.1
|
||||
Text Domain: gustavoo-portfolio
|
||||
License: GNU General Public License v2 or later
|
||||
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
*/
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* Empty result state.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
?>
|
||||
|
||||
<section class="no-results not-found" aria-labelledby="no-results-title">
|
||||
<h2 id="no-results-title" class="no-results__title"><?php esc_html_e( 'Nada encontrado', 'gustavoo-portfolio' ); ?></h2>
|
||||
<?php if ( is_search() ) : ?>
|
||||
<p><?php esc_html_e( 'Não encontramos conteúdo com esses termos. Tente palavras diferentes.', 'gustavoo-portfolio' ); ?></p>
|
||||
<?php get_search_form(); ?>
|
||||
<?php else : ?>
|
||||
<p><?php esc_html_e( 'Ainda não há publicações aqui.', 'gustavoo-portfolio' ); ?></p>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* Page content.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
?>
|
||||
|
||||
<article id="post-<?php the_ID(); ?>" <?php post_class( 'page-article' ); ?>>
|
||||
<header class="page-article__header">
|
||||
<p class="eyebrow"><?php esc_html_e( 'Página', 'gustavoo-portfolio' ); ?></p>
|
||||
<?php the_title( '<h1 class="page-article__title">', '</h1>' ); ?>
|
||||
</header>
|
||||
|
||||
<?php if ( has_post_thumbnail() ) : ?>
|
||||
<figure class="page-article__media">
|
||||
<?php the_post_thumbnail( 'full', array( 'class' => 'page-article__image' ) ); ?>
|
||||
</figure>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="entry-content prose">
|
||||
<?php the_content(); ?>
|
||||
<?php
|
||||
wp_link_pages(
|
||||
array(
|
||||
'before' => '<nav class="page-links" aria-label="' . esc_attr__( 'Páginas deste conteúdo', 'gustavoo-portfolio' ) . '"><span>' . esc_html__( 'Páginas:', 'gustavoo-portfolio' ) . '</span>',
|
||||
'after' => '</nav>',
|
||||
)
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
</article>
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* Front-page about section.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
$gustavoo_about_text = (string) gustavoo_portfolio_get_setting( 'about_text' );
|
||||
$gustavoo_about_secondary_text = (string) gustavoo_portfolio_get_setting( 'about_secondary_text' );
|
||||
?>
|
||||
|
||||
<section id="sobre" class="section section--about" aria-labelledby="about-title">
|
||||
<div class="site-shell about-layout">
|
||||
<header class="section__header about-layout__header">
|
||||
<p class="eyebrow"><?php echo esc_html( gustavoo_portfolio_get_setting( 'about_eyebrow' ) ); ?></p>
|
||||
<h2 id="about-title" class="section__title"><?php echo esc_html( gustavoo_portfolio_get_setting( 'about_title' ) ); ?></h2>
|
||||
</header>
|
||||
|
||||
<div class="about-layout__content">
|
||||
<div class="prose prose--lead">
|
||||
<?php echo wp_kses_post( wpautop( esc_html( $gustavoo_about_text ) ) ); ?>
|
||||
<?php echo wp_kses_post( wpautop( esc_html( $gustavoo_about_secondary_text ) ) ); ?>
|
||||
</div>
|
||||
|
||||
<dl class="about-facts" aria-label="<?php esc_attr_e( 'Diferenciais profissionais', 'gustavoo-portfolio' ); ?>">
|
||||
<div class="about-fact">
|
||||
<dt><?php esc_html_e( 'Escopo', 'gustavoo-portfolio' ); ?></dt>
|
||||
<dd><?php esc_html_e( 'Da regra de negócio à infraestrutura', 'gustavoo-portfolio' ); ?></dd>
|
||||
</div>
|
||||
<div class="about-fact">
|
||||
<dt><?php esc_html_e( 'Entrega', 'gustavoo-portfolio' ); ?></dt>
|
||||
<dd><?php esc_html_e( 'Código limpo, documentado e sustentável', 'gustavoo-portfolio' ); ?></dd>
|
||||
</div>
|
||||
<div class="about-fact">
|
||||
<dt><?php esc_html_e( 'Visão', 'gustavoo-portfolio' ); ?></dt>
|
||||
<dd><?php esc_html_e( 'Tecnologia alinhada ao crescimento', 'gustavoo-portfolio' ); ?></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* Front-page contact section.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
$gustavoo_contact_form_id = absint( gustavoo_portfolio_get_setting( 'contact_form_id' ) );
|
||||
$gustavoo_email = sanitize_email( (string) gustavoo_portfolio_get_setting( 'email' ) );
|
||||
$gustavoo_privacy_url = get_privacy_policy_url();
|
||||
?>
|
||||
|
||||
<section id="contato" class="section section--contact" aria-labelledby="contact-title">
|
||||
<div class="site-shell contact-panel">
|
||||
<div class="contact-panel__content">
|
||||
<p class="eyebrow"><?php echo esc_html( gustavoo_portfolio_get_setting( 'contact_eyebrow' ) ); ?></p>
|
||||
<h2 id="contact-title" class="section__title"><?php echo esc_html( gustavoo_portfolio_get_setting( 'contact_heading' ) ); ?></h2>
|
||||
<p class="contact-panel__description"><?php echo esc_html( gustavoo_portfolio_get_setting( 'contact_text' ) ); ?></p>
|
||||
|
||||
<?php if ( $gustavoo_email ) : ?>
|
||||
<ul class="contact-list">
|
||||
<?php if ( $gustavoo_email ) : ?>
|
||||
<li><span><?php esc_html_e( 'E-mail', 'gustavoo-portfolio' ); ?></span><a href="<?php echo esc_url( 'mailto:' . $gustavoo_email ); ?>"><?php echo esc_html( antispambot( $gustavoo_email ) ); ?></a></li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php gustavoo_portfolio_social_links( 'social-links social-links--contact' ); ?>
|
||||
</div>
|
||||
|
||||
<div class="contact-panel__form">
|
||||
<?php gustavoo_portfolio_the_fluent_form( $gustavoo_contact_form_id, 'contact' ); ?>
|
||||
<?php if ( $gustavoo_privacy_url ) : ?>
|
||||
<p class="form-privacy">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: privacy policy URL. */
|
||||
wp_kses_post( __( 'Ao enviar, você concorda com o tratamento dos dados conforme a <a href="%s">Política de Privacidade</a>.', 'gustavoo-portfolio' ) ),
|
||||
esc_url( $gustavoo_privacy_url )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* Front-page hero.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
$gustavoo_hero_eyebrow = (string) gustavoo_portfolio_get_setting( 'hero_eyebrow' );
|
||||
$gustavoo_hero_title = (string) gustavoo_portfolio_get_setting( 'hero_title' );
|
||||
$gustavoo_hero_description = (string) gustavoo_portfolio_get_setting( 'hero_description' );
|
||||
$gustavoo_hero_primary_label = (string) gustavoo_portfolio_get_setting( 'hero_primary_label' );
|
||||
$gustavoo_hero_primary_url = (string) gustavoo_portfolio_get_setting( 'hero_primary_url' );
|
||||
$gustavoo_hero_secondary_label = (string) gustavoo_portfolio_get_setting( 'hero_secondary_label' );
|
||||
$gustavoo_hero_secondary_url = (string) gustavoo_portfolio_get_setting( 'hero_secondary_url' );
|
||||
$gustavoo_hero_sources = gustavoo_portfolio_get_picture_sources( 'hero-art' );
|
||||
?>
|
||||
|
||||
<section class="hero-stage" aria-labelledby="hero-title">
|
||||
<div class="site-shell hero">
|
||||
<div class="hero__content">
|
||||
<?php if ( $gustavoo_hero_eyebrow ) : ?>
|
||||
<p class="eyebrow hero__eyebrow"><?php echo esc_html( $gustavoo_hero_eyebrow ); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<h1 id="hero-title" class="hero__title"><?php echo esc_html( $gustavoo_hero_title ); ?></h1>
|
||||
<p class="hero__description"><?php echo esc_html( $gustavoo_hero_description ); ?></p>
|
||||
|
||||
<div class="hero__actions">
|
||||
<?php if ( $gustavoo_hero_primary_label && $gustavoo_hero_primary_url ) : ?>
|
||||
<a class="button button--primary" href="<?php echo esc_url( $gustavoo_hero_primary_url ); ?>">
|
||||
<?php echo esc_html( $gustavoo_hero_primary_label ); ?> <span aria-hidden="true">→</span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( $gustavoo_hero_secondary_label && $gustavoo_hero_secondary_url ) : ?>
|
||||
<a class="button button--secondary" href="<?php echo esc_url( $gustavoo_hero_secondary_url ); ?>">
|
||||
<?php echo esc_html( $gustavoo_hero_secondary_label ); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="hero__visual" aria-hidden="true">
|
||||
<div class="hero__visual-glow"></div>
|
||||
<picture>
|
||||
<?php if ( $gustavoo_hero_sources['webp'] ) : ?>
|
||||
<source srcset="<?php echo esc_url( $gustavoo_hero_sources['webp'] ); ?>" type="image/webp">
|
||||
<?php endif; ?>
|
||||
<img class="hero__image" src="<?php echo esc_url( $gustavoo_hero_sources['fallback'] ); ?>" width="1234" height="1280" alt="" loading="eager" decoding="async" fetchpriority="high">
|
||||
</picture>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* Front-page latest posts.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
$gustavoo_blog_limit = max( 1, min( 12, absint( gustavoo_portfolio_get_setting( 'blog_limit', 4 ) ) ) );
|
||||
$gustavoo_posts = new WP_Query(
|
||||
array(
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $gustavoo_blog_limit,
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => true,
|
||||
)
|
||||
);
|
||||
?>
|
||||
|
||||
<section id="blog" class="section section--posts" aria-labelledby="latest-posts-title">
|
||||
<div class="site-shell">
|
||||
<header class="section__header section__header--split">
|
||||
<div>
|
||||
<p class="eyebrow"><?php echo esc_html( gustavoo_portfolio_get_setting( 'blog_eyebrow' ) ); ?></p>
|
||||
<h2 id="latest-posts-title" class="section__title"><?php echo esc_html( gustavoo_portfolio_get_setting( 'blog_title' ) ); ?></h2>
|
||||
</div>
|
||||
<p class="section__description"><?php echo esc_html( gustavoo_portfolio_get_setting( 'blog_text' ) ); ?></p>
|
||||
</header>
|
||||
|
||||
<?php if ( $gustavoo_posts->have_posts() ) : ?>
|
||||
<div class="posts-grid posts-grid--featured">
|
||||
<?php
|
||||
while ( $gustavoo_posts->have_posts() ) {
|
||||
$gustavoo_posts->the_post();
|
||||
get_template_part( 'template-parts/global/post-card' );
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
<p class="section__action"><a class="button button--secondary" href="<?php echo esc_url( gustavoo_portfolio_get_blog_url() ); ?>"><?php esc_html_e( 'Explorar todos os artigos', 'gustavoo-portfolio' ); ?> <span aria-hidden="true">→</span></a></p>
|
||||
<?php else : ?>
|
||||
<div class="section__empty"><p><?php esc_html_e( 'O primeiro artigo está a caminho.', 'gustavoo-portfolio' ); ?></p></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php wp_reset_postdata(); ?>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* Front-page project grid.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
$gustavoo_project_limit = max( 1, min( 24, absint( gustavoo_portfolio_get_setting( 'projects_limit', 6 ) ) ) );
|
||||
$gustavoo_projects = gustavoo_portfolio_get_home_projects( $gustavoo_project_limit );
|
||||
$gustavoo_archive_url = esc_url_raw( (string) gustavoo_portfolio_get_setting( 'projects_archive_url' ) );
|
||||
?>
|
||||
|
||||
<section id="projetos" class="section section--projects" aria-labelledby="projects-title">
|
||||
<div class="site-shell">
|
||||
<header class="section__header section__header--split">
|
||||
<div>
|
||||
<p class="eyebrow"><?php echo esc_html( gustavoo_portfolio_get_setting( 'projects_eyebrow' ) ); ?></p>
|
||||
<h2 id="projects-title" class="section__title"><?php echo esc_html( gustavoo_portfolio_get_setting( 'projects_title' ) ); ?></h2>
|
||||
</div>
|
||||
<p class="section__description"><?php echo esc_html( gustavoo_portfolio_get_setting( 'projects_text' ) ); ?></p>
|
||||
</header>
|
||||
|
||||
<?php if ( $gustavoo_projects ) : ?>
|
||||
<div class="projects-grid">
|
||||
<?php
|
||||
global $post;
|
||||
|
||||
foreach ( $gustavoo_projects as $post ) {
|
||||
setup_postdata( $post );
|
||||
get_template_part( 'template-parts/global/project-card' );
|
||||
}
|
||||
|
||||
wp_reset_postdata();
|
||||
?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class="section__empty">
|
||||
<p><?php esc_html_e( 'Novos projetos serão publicados aqui em breve.', 'gustavoo-portfolio' ); ?></p>
|
||||
<?php if ( current_user_can( 'edit_posts' ) && post_type_exists( 'gso_project' ) ) : ?>
|
||||
<a class="text-link" href="<?php echo esc_url( admin_url( 'post-new.php?post_type=gso_project' ) ); ?>"><?php esc_html_e( 'Adicionar o primeiro projeto', 'gustavoo-portfolio' ); ?> <span aria-hidden="true">→</span></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( $gustavoo_archive_url ) : ?>
|
||||
<p class="section__action"><a class="button button--secondary" href="<?php echo esc_url( $gustavoo_archive_url ); ?>"><?php esc_html_e( 'Ver todos os projetos', 'gustavoo-portfolio' ); ?> <span aria-hidden="true">→</span></a></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* Front-page services section.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
$gustavoo_services = gustavoo_portfolio_get_services();
|
||||
?>
|
||||
|
||||
<section id="servicos" class="section section--services" aria-labelledby="services-title">
|
||||
<div class="site-shell">
|
||||
<header class="section__header section__header--split">
|
||||
<div>
|
||||
<p class="eyebrow"><?php echo esc_html( gustavoo_portfolio_get_setting( 'services_eyebrow' ) ); ?></p>
|
||||
<h2 id="services-title" class="section__title"><?php echo esc_html( gustavoo_portfolio_get_setting( 'services_title' ) ); ?></h2>
|
||||
</div>
|
||||
<p class="section__description"><?php echo esc_html( gustavoo_portfolio_get_setting( 'services_description' ) ); ?></p>
|
||||
</header>
|
||||
|
||||
<div class="services-grid">
|
||||
<?php foreach ( $gustavoo_services as $gustavoo_service ) : ?>
|
||||
<article class="service-card">
|
||||
<p class="service-card__number" aria-hidden="true"><?php echo esc_html( $gustavoo_service['number'] ?? '' ); ?></p>
|
||||
<h3 class="service-card__title"><?php echo esc_html( $gustavoo_service['title'] ?? '' ); ?></h3>
|
||||
<p class="service-card__description"><?php echo esc_html( $gustavoo_service['description'] ?? '' ); ?></p>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/** Compact editorial news card. @package Gustavo_Portfolio */
|
||||
|
||||
$gustavoo_args = isset( $args ) && is_array( $args ) ? $args : array();
|
||||
$gustavoo_is_featured = ! empty( $gustavoo_args['featured'] );
|
||||
$gustavoo_categories = get_the_category();
|
||||
$gustavoo_category = $gustavoo_categories ? $gustavoo_categories[0] : null;
|
||||
$gustavoo_image_url = gustavoo_portfolio_get_news_image_url( get_the_ID(), $gustavoo_is_featured ? 'large' : 'gustavoo-post-card' );
|
||||
?>
|
||||
<article <?php post_class( 'latest-news-card' . ( $gustavoo_is_featured ? ' latest-news-card--featured' : '' ) ); ?>>
|
||||
<a class="latest-news-card__media" href="<?php the_permalink(); ?>" aria-hidden="true" tabindex="-1">
|
||||
<?php if ( $gustavoo_image_url ) : ?>
|
||||
<img class="latest-news-card__image" src="<?php echo esc_url( $gustavoo_image_url ); ?>" loading="<?php echo $gustavoo_is_featured ? 'eager' : 'lazy'; ?>" decoding="async" alt="">
|
||||
<?php else : ?><span class="latest-news-card__placeholder" aria-hidden="true"></span><?php endif; ?>
|
||||
</a>
|
||||
<div class="latest-news-card__body">
|
||||
<?php if ( $gustavoo_category ) : ?><a class="latest-news-card__category" href="<?php echo esc_url( get_category_link( $gustavoo_category ) ); ?>"><?php echo esc_html( $gustavoo_category->name ); ?></a><?php endif; ?>
|
||||
<h2 class="latest-news-card__title"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
|
||||
<time class="latest-news-card__date" datetime="<?php echo esc_attr( get_the_date( DATE_W3C ) ); ?>"><?php echo esc_html( get_the_date() ); ?></time>
|
||||
<?php if ( $gustavoo_is_featured && has_excerpt() ) : ?><p class="latest-news-card__excerpt"><?php echo esc_html( wp_trim_words( get_the_excerpt(), 28 ) ); ?></p><?php endif; ?>
|
||||
</div>
|
||||
</article>
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* Secondary compact news list for news and category pages.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
$gustavoo_args = isset( $args ) && is_array( $args ) ? $args : array();
|
||||
$gustavoo_query_args = array(
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => 4,
|
||||
'post__not_in' => array_map( 'absint', $gustavoo_args['exclude'] ?? array() ),
|
||||
'ignore_sticky_posts' => true,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
);
|
||||
|
||||
if ( ! empty( $gustavoo_args['category_id'] ) ) {
|
||||
$gustavoo_query_args['cat'] = absint( $gustavoo_args['category_id'] );
|
||||
}
|
||||
|
||||
$gustavoo_latest_posts = new WP_Query( $gustavoo_query_args );
|
||||
|
||||
if ( ! $gustavoo_latest_posts->have_posts() ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<section class="latest-news-list" aria-labelledby="latest-news-list-title">
|
||||
<h2 id="latest-news-list-title" class="latest-news-list__heading"><?php esc_html_e( 'Últimas notícias', 'gustavoo-portfolio' ); ?></h2>
|
||||
<div class="latest-news-list__items">
|
||||
<?php while ( $gustavoo_latest_posts->have_posts() ) : $gustavoo_latest_posts->the_post(); ?>
|
||||
<?php $gustavoo_categories = get_the_category(); $gustavoo_category = $gustavoo_categories ? $gustavoo_categories[0] : null; $gustavoo_image_url = gustavoo_portfolio_get_news_image_url(); ?>
|
||||
<article <?php post_class( 'latest-news-list__item' ); ?>>
|
||||
<a class="latest-news-list__image" href="<?php the_permalink(); ?>" aria-hidden="true" tabindex="-1">
|
||||
<?php if ( $gustavoo_image_url ) : ?><img src="<?php echo esc_url( $gustavoo_image_url ); ?>" loading="lazy" decoding="async" alt=""><?php endif; ?>
|
||||
</a>
|
||||
<div class="latest-news-list__body">
|
||||
<?php if ( $gustavoo_category ) : ?><a class="latest-news-card__category" href="<?php echo esc_url( get_category_link( $gustavoo_category ) ); ?>"><?php echo esc_html( $gustavoo_category->name ); ?></a><?php endif; ?>
|
||||
<h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
|
||||
<time datetime="<?php echo esc_attr( get_the_date( DATE_W3C ) ); ?>"><?php echo esc_html( get_the_date( 'D, d/m/Y · H:i' ) ); ?></time>
|
||||
</div>
|
||||
</article>
|
||||
<?php endwhile; ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php wp_reset_postdata(); ?>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* Global newsletter call to action, rendered before every site footer.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
$gustavoo_newsletter_form_id = absint( gustavoo_portfolio_get_setting( 'newsletter_form_id' ) );
|
||||
$gustavoo_privacy_url = get_privacy_policy_url();
|
||||
?>
|
||||
|
||||
<section class="section section--newsletter" aria-labelledby="newsletter-title">
|
||||
<div class="site-shell newsletter-panel">
|
||||
<div class="newsletter-panel__content">
|
||||
<p class="eyebrow"><?php echo esc_html( gustavoo_portfolio_get_setting( 'newsletter_eyebrow' ) ); ?></p>
|
||||
<h2 id="newsletter-title" class="newsletter-panel__title"><?php echo esc_html( gustavoo_portfolio_get_setting( 'newsletter_heading' ) ); ?></h2>
|
||||
<p class="newsletter-panel__description"><?php echo esc_html( gustavoo_portfolio_get_setting( 'newsletter_text' ) ); ?></p>
|
||||
</div>
|
||||
|
||||
<div class="newsletter-panel__form">
|
||||
<?php gustavoo_portfolio_the_fluent_form( $gustavoo_newsletter_form_id, 'newsletter' ); ?>
|
||||
<?php if ( $gustavoo_privacy_url ) : ?>
|
||||
<p class="form-privacy">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: privacy policy URL. */
|
||||
wp_kses_post( __( 'Você pode cancelar a inscrição a qualquer momento. Consulte a <a href="%s">Política de Privacidade</a>.', 'gustavoo-portfolio' ) ),
|
||||
esc_url( $gustavoo_privacy_url )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* Blog post card.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
$gustavoo_categories = get_the_category();
|
||||
$gustavoo_category = $gustavoo_categories ? $gustavoo_categories[0] : null;
|
||||
?>
|
||||
|
||||
<article <?php post_class( 'post-card' ); ?>>
|
||||
<?php if ( has_post_thumbnail() ) : ?>
|
||||
<a class="post-card__media" href="<?php the_permalink(); ?>" tabindex="-1" aria-hidden="true">
|
||||
<?php
|
||||
the_post_thumbnail(
|
||||
'gustavoo-post-card',
|
||||
array(
|
||||
'class' => 'post-card__image',
|
||||
'loading' => 'lazy',
|
||||
'decoding' => 'async',
|
||||
'alt' => '',
|
||||
)
|
||||
);
|
||||
?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="post-card__body">
|
||||
<div class="post-card__meta">
|
||||
<?php if ( $gustavoo_category ) : ?>
|
||||
<a class="post-card__category" href="<?php echo esc_url( get_category_link( $gustavoo_category ) ); ?>"><?php echo esc_html( $gustavoo_category->name ); ?></a>
|
||||
<?php endif; ?>
|
||||
<?php gustavoo_portfolio_posted_on(); ?>
|
||||
<span class="reading-time"><?php echo esc_html( gustavoo_portfolio_reading_time() ); ?></span>
|
||||
</div>
|
||||
|
||||
<h2 class="post-card__title"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
|
||||
<p class="post-card__excerpt"><?php echo esc_html( wp_trim_words( get_the_excerpt(), 26 ) ); ?></p>
|
||||
<a class="post-card__more" href="<?php the_permalink(); ?>">
|
||||
<?php esc_html_e( 'Ler artigo', 'gustavoo-portfolio' ); ?> <span aria-hidden="true">→</span>
|
||||
<span class="screen-reader-text">: <?php the_title(); ?></span>
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/**
|
||||
* Project card.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
$gustavoo_project_id = get_the_ID();
|
||||
$gustavoo_project_url = gustavoo_portfolio_validate_project_url( get_post_meta( $gustavoo_project_id, '_gso_project_url', true ) );
|
||||
$gustavoo_project_client = sanitize_text_field( (string) get_post_meta( $gustavoo_project_id, '_gso_project_client', true ) );
|
||||
$gustavoo_project_year = absint( get_post_meta( $gustavoo_project_id, '_gso_project_year', true ) );
|
||||
$gustavoo_project_link_label = sanitize_text_field( (string) get_post_meta( $gustavoo_project_id, '_gso_project_link_label', true ) );
|
||||
$gustavoo_project_link_label = $gustavoo_project_link_label ?: __( 'Ver projeto', 'gustavoo-portfolio' );
|
||||
$gustavoo_project_types = taxonomy_exists( 'gso_project_type' ) ? get_the_terms( $gustavoo_project_id, 'gso_project_type' ) : array();
|
||||
$gustavoo_project_tech = taxonomy_exists( 'gso_project_tech' ) ? get_the_terms( $gustavoo_project_id, 'gso_project_tech' ) : array();
|
||||
|
||||
if ( ! $gustavoo_project_url ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
|
||||
<article <?php post_class( 'project-card' ); ?>>
|
||||
<a class="project-card__link" href="<?php echo esc_url( $gustavoo_project_url ); ?>" target="_blank" rel="noopener noreferrer external">
|
||||
<figure class="project-card__media">
|
||||
<?php if ( has_post_thumbnail() ) : ?>
|
||||
<?php
|
||||
$gustavoo_thumbnail_id = get_post_thumbnail_id();
|
||||
$gustavoo_thumbnail_alt = get_post_meta( $gustavoo_thumbnail_id, '_wp_attachment_image_alt', true );
|
||||
$gustavoo_thumbnail_alt = $gustavoo_thumbnail_alt ?: sprintf(
|
||||
/* translators: %s: project title. */
|
||||
__( 'Imagem do projeto %s', 'gustavoo-portfolio' ),
|
||||
get_the_title()
|
||||
);
|
||||
|
||||
echo get_the_post_thumbnail(
|
||||
$gustavoo_project_id,
|
||||
'gustavoo-project-card',
|
||||
array(
|
||||
'alt' => $gustavoo_thumbnail_alt,
|
||||
'class' => 'project-card__image',
|
||||
'loading' => 'lazy',
|
||||
'decoding' => 'async',
|
||||
)
|
||||
); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Core image markup.
|
||||
?>
|
||||
<?php else : ?>
|
||||
<span class="project-card__placeholder" aria-hidden="true">
|
||||
<img src="<?php echo esc_url( gustavoo_portfolio_get_image_source( 'logo-mark' ) ); ?>" width="96" height="96" alt="">
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</figure>
|
||||
|
||||
<div class="project-card__body">
|
||||
<?php if ( ! is_wp_error( $gustavoo_project_types ) && $gustavoo_project_types ) : ?>
|
||||
<p class="project-card__type"><?php echo esc_html( implode( ' · ', wp_list_pluck( $gustavoo_project_types, 'name' ) ) ); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<h3 class="project-card__title"><?php the_title(); ?></h3>
|
||||
|
||||
<?php if ( has_excerpt() ) : ?>
|
||||
<p class="project-card__description"><?php echo esc_html( wp_trim_words( get_the_excerpt(), 24 ) ); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( $gustavoo_project_client || $gustavoo_project_year ) : ?>
|
||||
<p class="project-card__meta">
|
||||
<?php echo esc_html( implode( ' · ', array_filter( array( $gustavoo_project_client, $gustavoo_project_year ? (string) $gustavoo_project_year : '' ) ) ) ); ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( ! is_wp_error( $gustavoo_project_tech ) && $gustavoo_project_tech ) : ?>
|
||||
<ul class="project-card__tags" aria-label="<?php esc_attr_e( 'Tecnologias', 'gustavoo-portfolio' ); ?>">
|
||||
<?php foreach ( array_slice( $gustavoo_project_tech, 0, 4 ) as $gustavoo_technology ) : ?>
|
||||
<li><?php echo esc_html( $gustavoo_technology->name ); ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
|
||||
<span class="project-card__cta">
|
||||
<?php echo esc_html( $gustavoo_project_link_label ); ?> <span aria-hidden="true">↗</span>
|
||||
<span class="screen-reader-text"> <?php esc_html_e( '(abre em uma nova guia)', 'gustavoo-portfolio' ); ?></span>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* Floating WhatsApp action used by the Customizer partial refresh.
|
||||
*
|
||||
* @package Gustavo_Portfolio
|
||||
*/
|
||||
|
||||
gustavoo_portfolio_the_whatsapp_float();
|
||||
@@ -0,0 +1,119 @@
|
||||
{
|
||||
"$schema": "https://schemas.wp.org/trunk/theme.json",
|
||||
"version": 3,
|
||||
"settings": {
|
||||
"appearanceTools": true,
|
||||
"layout": {
|
||||
"contentSize": "760px",
|
||||
"wideSize": "1160px"
|
||||
},
|
||||
"color": {
|
||||
"defaultPalette": false,
|
||||
"palette": [
|
||||
{ "slug": "background", "name": "Fundo", "color": "#071624" },
|
||||
{ "slug": "background-alt", "name": "Fundo alternativo", "color": "#0a1c2d" },
|
||||
{ "slug": "surface", "name": "Superfície", "color": "#0d2438" },
|
||||
{ "slug": "text", "name": "Texto", "color": "#f5f8fa" },
|
||||
{ "slug": "text-soft", "name": "Texto suave", "color": "#b8c5ce" },
|
||||
{ "slug": "brand", "name": "Menta", "color": "#74d5c7" },
|
||||
{ "slug": "brand-strong", "name": "Menta forte", "color": "#50e5cf" }
|
||||
]
|
||||
},
|
||||
"spacing": {
|
||||
"spacingScale": {
|
||||
"steps": 0
|
||||
},
|
||||
"spacingSizes": [
|
||||
{ "slug": "10", "name": "XS", "size": "0.5rem" },
|
||||
{ "slug": "20", "name": "S", "size": "0.75rem" },
|
||||
{ "slug": "30", "name": "M", "size": "1rem" },
|
||||
{ "slug": "40", "name": "L", "size": "1.5rem" },
|
||||
{ "slug": "50", "name": "XL", "size": "2rem" },
|
||||
{ "slug": "60", "name": "2XL", "size": "3rem" },
|
||||
{ "slug": "70", "name": "3XL", "size": "4rem" }
|
||||
],
|
||||
"units": ["px", "rem", "em", "%", "vw", "vh"]
|
||||
},
|
||||
"typography": {
|
||||
"fluid": true,
|
||||
"fontFamilies": [
|
||||
{
|
||||
"fontFamily": "Inter, system-ui, sans-serif",
|
||||
"name": "Inter",
|
||||
"slug": "inter",
|
||||
"fontFace": [
|
||||
{
|
||||
"fontFamily": "Inter",
|
||||
"fontStyle": "normal",
|
||||
"fontWeight": "100 900",
|
||||
"src": ["file:./assets/fonts/inter-variable.woff2"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"fontFamily": "SFMono-Regular, Consolas, Liberation Mono, monospace",
|
||||
"name": "Monoespaçada",
|
||||
"slug": "mono"
|
||||
}
|
||||
],
|
||||
"fontSizes": [
|
||||
{ "slug": "small", "name": "Pequeno", "size": "0.875rem" },
|
||||
{ "slug": "medium", "name": "Médio", "size": "1rem" },
|
||||
{ "slug": "large", "name": "Grande", "size": "1.25rem" },
|
||||
{ "slug": "x-large", "name": "Muito grande", "size": "clamp(1.75rem, 3vw, 2.75rem)" },
|
||||
{ "slug": "hero", "name": "Hero", "size": "clamp(2.7rem, 5.4vw, 5.3rem)" }
|
||||
]
|
||||
},
|
||||
"border": {
|
||||
"color": true,
|
||||
"radius": true,
|
||||
"style": true,
|
||||
"width": true
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"color": {
|
||||
"background": "#071624",
|
||||
"text": "#f5f8fa"
|
||||
},
|
||||
"typography": {
|
||||
"fontFamily": "var(--wp--preset--font-family--inter)",
|
||||
"fontSize": "1.0625rem",
|
||||
"lineHeight": "1.72"
|
||||
},
|
||||
"elements": {
|
||||
"button": {
|
||||
"border": { "radius": "12px" },
|
||||
"color": { "background": "#74d5c7", "text": "#062722" },
|
||||
"typography": { "fontWeight": "700" }
|
||||
},
|
||||
"caption": {
|
||||
"color": { "text": "#879aa9" },
|
||||
"typography": { "fontSize": "0.8rem" }
|
||||
},
|
||||
"heading": {
|
||||
"color": { "text": "#f5f8fa" },
|
||||
"typography": {
|
||||
"fontFamily": "var(--wp--preset--font-family--inter)",
|
||||
"fontWeight": "750",
|
||||
"lineHeight": "1.1"
|
||||
}
|
||||
},
|
||||
"link": {
|
||||
"color": { "text": "#74d5c7" }
|
||||
}
|
||||
},
|
||||
"blocks": {
|
||||
"core/code": {
|
||||
"border": { "color": "rgba(142,190,207,.18)", "radius": "10px", "width": "1px" },
|
||||
"color": { "background": "#040d16", "text": "#d9fdf7" },
|
||||
"typography": { "fontFamily": "var(--wp--preset--font-family--mono)", "fontSize": "0.9rem" }
|
||||
},
|
||||
"core/quote": {
|
||||
"border": { "color": "#74d5c7", "style": "solid", "width": "0 0 0 3px" },
|
||||
"color": { "text": "#b8c5ce" },
|
||||
"spacing": { "padding": { "left": "1.5rem" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<?php
|
||||
// Silence is golden.
|
||||