diff --git a/modules/site_users_microsite/site_users_microsite.module b/modules/site_users_microsite/site_users_microsite.module
index fdd43fc..3da6950 100644
--- a/modules/site_users_microsite/site_users_microsite.module
+++ b/modules/site_users_microsite/site_users_microsite.module
@@ -7,6 +7,24 @@
use Drupal\user\UserInterface;
+/**
+ * Implements hook_theme().
+ */
+function site_users_microsite_theme(): array {
+ return [
+ 'microsite_header_block' => [
+ 'variables' => [
+ 'photo_url' => NULL,
+ 'photo_alt' => '',
+ 'name' => NULL,
+ 'bio' => NULL,
+ 'phone' => NULL,
+ 'email' => NULL,
+ ],
+ ],
+ ];
+}
+
/**
* Implements hook_preprocess_page().
*
diff --git a/modules/site_users_microsite/src/Plugin/Block/MicrositeHeaderBlock.php b/modules/site_users_microsite/src/Plugin/Block/MicrositeHeaderBlock.php
new file mode 100644
index 0000000..946f94e
--- /dev/null
+++ b/modules/site_users_microsite/src/Plugin/Block/MicrositeHeaderBlock.php
@@ -0,0 +1,166 @@
+get('current_route_match'),
+ $container->get('entity_type.manager'),
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function defaultConfiguration(): array {
+ return ['label_display' => '0'] + parent::defaultConfiguration();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function build(): array {
+ $user = $this->getUser();
+ if (!$user instanceof UserInterface) {
+ return [];
+ }
+
+ return [
+ '#theme' => 'microsite_header_block',
+ '#photo_url' => $this->getPhotoUrl($user),
+ '#photo_alt' => $this->getPhotoAlt($user),
+ '#name' => $this->getFieldValue($user, 'field_user_name') ?: $user->getDisplayName(),
+ '#bio' => $this->getProcessedValue($user, 'field_user_bio'),
+ '#phone' => $this->getFieldValue($user, 'field_user_phone'),
+ '#email' => $user->getEmail(),
+ '#cache' => [
+ 'tags' => $user->getCacheTags(),
+ 'contexts' => ['route'],
+ ],
+ ];
+ }
+
+ /**
+ * Retorna o usuário da rota atual.
+ */
+ protected function getUser(): ?UserInterface {
+ $user = $this->routeMatch->getParameter('user');
+ if ($user instanceof UserInterface) {
+ return $user;
+ }
+ if (is_numeric($user)) {
+ return $this->entityTypeManager->getStorage('user')->load($user);
+ }
+ return NULL;
+ }
+
+ /**
+ * Retorna a URL absoluta da foto padrão do usuário.
+ */
+ protected function getPhotoUrl(UserInterface $user): ?string {
+ if (!function_exists('site_users_get_default_photo')) {
+ return NULL;
+ }
+ $media = site_users_get_default_photo($user);
+ if (!$media) {
+ return NULL;
+ }
+ $source_field = $media->getSource()->getConfiguration()['source_field'];
+ if (!$media->hasField($source_field) || $media->get($source_field)->isEmpty()) {
+ return NULL;
+ }
+ $file = $media->get($source_field)->entity;
+ if (!$file) {
+ return NULL;
+ }
+ return \Drupal::service('file_url_generator')->generateAbsoluteString($file->getFileUri());
+ }
+
+ /**
+ * Retorna o texto alternativo da foto padrão.
+ */
+ protected function getPhotoAlt(UserInterface $user): string {
+ if (!function_exists('site_users_get_default_photo')) {
+ return '';
+ }
+ $media = site_users_get_default_photo($user);
+ return $media ? $media->label() : '';
+ }
+
+ /**
+ * Retorna o valor de texto de um campo do usuário.
+ */
+ protected function getFieldValue(UserInterface $user, string $field_name): ?string {
+ if ($user->hasField($field_name) && !$user->get($field_name)->isEmpty()) {
+ return $user->get($field_name)->value;
+ }
+ return NULL;
+ }
+
+ /**
+ * Retorna o valor processado (HTML filtrado) de um campo text_long.
+ *
+ * Usa ->processed, que aplica o formato de texto configurado e retorna um
+ * objeto Markup — o Twig renderiza como HTML sem escape adicional.
+ */
+ protected function getProcessedValue(UserInterface $user, string $field_name): ?string {
+ if ($user->hasField($field_name) && !$user->get($field_name)->isEmpty()) {
+ return $user->get($field_name)->processed;
+ }
+ return NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheTags(): array {
+ $user = $this->getUser();
+ if ($user instanceof UserInterface) {
+ return Cache::mergeTags(parent::getCacheTags(), $user->getCacheTags());
+ }
+ return parent::getCacheTags();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheContexts(): array {
+ return Cache::mergeContexts(parent::getCacheContexts(), ['route']);
+ }
+
+}
diff --git a/modules/site_users_microsite/templates/microsite-header-block.html.twig b/modules/site_users_microsite/templates/microsite-header-block.html.twig
new file mode 100644
index 0000000..4a1a8d8
--- /dev/null
+++ b/modules/site_users_microsite/templates/microsite-header-block.html.twig
@@ -0,0 +1,59 @@
+{#
+/**
+ * @file
+ * Template do bloco de cabeçalho do microsite.
+ *
+ * Variáveis:
+ * - photo_url: URL absoluta da foto padrão (string|null).
+ * - photo_alt: Texto alternativo da foto (string).
+ * - name: Nome completo do usuário (string).
+ * - roles: Array de rótulos das roles do usuário (string[]).
+ * - bio: Biografia (string|null).
+ * - phone: Telefone (string|null).
+ * - email: E-mail (string|null).
+ */
+#}
+
diff --git a/site_users.module b/site_users.module
index 64cea82..22ca1cd 100644
--- a/site_users.module
+++ b/site_users.module
@@ -32,13 +32,7 @@ function site_users_theme($existing, $type, $theme, $path) {
'template' => 'user--restricted',
'base hook' => 'user',
],
- 'site_users_info_block' => [
- 'template' => 'site-user-info-block',
- 'variables' => [
- 'user_info' => [],
- 'user' => NULL,
- ],
- ],
+
];
}
diff --git a/src/Plugin/Block/UserInfoBlock.php b/src/Plugin/Block/UserInfoBlock.php
deleted file mode 100644
index 3832a8f..0000000
--- a/src/Plugin/Block/UserInfoBlock.php
+++ /dev/null
@@ -1,219 +0,0 @@
-routeMatch = $route_match;
- $this->entityTypeManager = $entity_type_manager;
- }
-
- /**
- * {@inheritdoc}
- */
- public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
- return new static(
- $configuration,
- $plugin_id,
- $plugin_definition,
- $container->get('current_route_match'),
- $container->get('entity_type.manager')
- );
- }
-
- /**
- * {@inheritdoc}
- */
- public function build() {
- $user = $this->getUserFromContext();
-
- if (!$user instanceof UserInterface) {
- return [];
- }
-
- // Obter foto padrão.
- $default_photo = NULL;
- $default_photo_url = NULL;
- if (function_exists('site_users_get_default_photo')) {
- $default_photo = site_users_get_default_photo($user);
- if ($default_photo) {
- // Usar o source field do media (forma padrão do Drupal).
- $source_field = $default_photo->getSource()->getConfiguration()['source_field'];
- if ($default_photo->hasField($source_field) && !$default_photo->get($source_field)->isEmpty()) {
- $file = $default_photo->get($source_field)->entity;
- if ($file) {
- $default_photo_url = \Drupal::service('file_url_generator')->generateAbsoluteString($file->getFileUri());
- }
- }
- }
- }
-
- // Coletar informações do usuário.
- $user_info = [
- 'uid' => $user->id(),
- 'username' => $user->getDisplayName(),
- 'name' => $this->getFieldValue($user, 'field_user_name'),
- 'phone' => $this->getFieldValue($user, 'field_user_phone'),
- 'bio' => $this->getFieldValue($user, 'field_user_bio'),
- 'social_links' => $this->getSocialLinks($user),
- 'photo_url' => $default_photo_url,
- 'photo_alt' => $default_photo ? $default_photo->label() : '',
- ];
-
- return [
- '#theme' => 'site_users_info_block',
- '#user_info' => $user_info,
- '#user' => $user,
- '#attached' => [
- 'library' => [
- 'site_users/user-info-block',
- ],
- ],
- ];
- }
-
- /**
- * Obtém o usuário da rota atual.
- *
- * @return \Drupal\user\UserInterface|null
- * O usuário ou NULL se não estiver em uma página de usuário.
- */
- protected function getUserFromContext(): ?UserInterface {
- // Obter da rota /user/{user}.
- $user = $this->routeMatch->getParameter('user');
-
- if ($user instanceof UserInterface) {
- return $user;
- }
-
- // Se for apenas o ID, carregar o usuário.
- if (is_numeric($user)) {
- return $this->entityTypeManager->getStorage('user')->load($user);
- }
-
- return NULL;
- }
-
- /**
- * Returns the social network links of a user.
- *
- * @param \Drupal\user\UserInterface $user
- * The user entity.
- *
- * @return array
- * Array of items with 'network' and 'url' keys.
- */
- protected function getSocialLinks(UserInterface $user): array {
- $links = [];
-
- if (!$user->hasField('field_user_social_links') || $user->get('field_user_social_links')->isEmpty()) {
- return $links;
- }
-
- foreach ($user->get('field_user_social_links') as $item) {
- if (!$item->isEmpty()) {
- $links[] = [
- 'network' => $item->network,
- 'url' => $item->url,
- ];
- }
- }
-
- return $links;
- }
-
- /**
- * Obtém o valor de um campo do usuário.
- *
- * @param \Drupal\user\UserInterface $user
- * O usuário.
- * @param string $field_name
- * O nome do campo.
- *
- * @return string|null
- * O valor do campo ou NULL.
- */
- protected function getFieldValue(UserInterface $user, string $field_name): ?string {
- if ($user->hasField($field_name) && !$user->get($field_name)->isEmpty()) {
- return $user->get($field_name)->value;
- }
- return NULL;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getCacheTags() {
- $tags = parent::getCacheTags();
-
- $user = $this->getUserFromContext();
- if ($user instanceof UserInterface) {
- $tags = Cache::mergeTags($tags, $user->getCacheTags());
- }
-
- return $tags;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getCacheContexts() {
- return Cache::mergeContexts(parent::getCacheContexts(), ['route', 'user']);
- }
-
-}
diff --git a/templates/site-user-info-block.html.twig b/templates/site-user-info-block.html.twig
deleted file mode 100644
index 7e20d4a..0000000
--- a/templates/site-user-info-block.html.twig
+++ /dev/null
@@ -1,63 +0,0 @@
-{#
-/**
- * @file
- * Template for the user information block.
- *
- * Available variables:
- * - user_info: Array with user information:
- * - uid: User ID
- * - username: Display name
- * - name: Full name
- * - phone: Phone number
- * - bio: Biography
- * - social_links: Array of social links, each with 'network' and 'url' keys
- * - photo_url: Default photo URL
- * - photo_alt: Photo alternative text
- * - user: User entity.
- */
-#}
-
-
- {% if user_info.photo_url %}
-

- {% else %}
-
-
- {{ user_info.name ? user_info.name|first|upper : user_info.username|first|upper }}
-
-
- {% endif %}
-
-
-
-
- {{ user_info.name ?: user_info.username }}
-
-
- {% if user_info.phone %}
-
- {% endif %}
-
- {% if user_info.bio %}
-
- {{ user_info.bio }}
-
- {% endif %}
-
- {% if user_info.social_links %}
-
- {% endif %}
-
-
diff --git a/themes/site_users_microsite_theme/css/microsite.css b/themes/site_users_microsite_theme/css/microsite.css
index 7f539d8..6fd2863 100644
--- a/themes/site_users_microsite_theme/css/microsite.css
+++ b/themes/site_users_microsite_theme/css/microsite.css
@@ -154,6 +154,105 @@ body.microsite {
margin-bottom: 1.5rem;
}
+/* Microsite Header Block --------------------------------------------------- */
+/*
+ * Bloco .msite-header-block: foto circular, nome (h1), meta (roles +
+ * departamento), biografia e lista de contatos.
+ * Assume fundo escuro herdado do .microsite-header.
+ */
+
+.msite-header-block {
+ display: flex;
+ align-items: flex-start;
+ gap: 1.75rem;
+ max-width: 1500px;
+ margin: 0 auto;
+ padding: 2rem 1.5rem;
+ color: #fff;
+}
+
+/* --- Foto ----------------------------------------------------------------- */
+
+.msite-header-block__photo-wrap {
+ flex-shrink: 0;
+}
+
+.msite-header-block__photo {
+ display: block;
+ width: 220px;
+ height: 280px;
+ border-radius: 50%;
+ object-fit: cover;
+ border: 3px solid rgba(255, 255, 255, 0.35);
+}
+
+/* Fallback de iniciais quando não há foto */
+.msite-header-block__photo--initials {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(255, 255, 255, 0.15);
+ font-size: 4rem;
+ font-weight: 700;
+ color: #fff;
+ user-select: none;
+}
+
+/* --- Informações ---------------------------------------------------------- */
+
+.msite-header-block__info {
+ flex: 1;
+ min-width: 0;
+}
+
+.msite-header-block__name {
+ margin: 0 0 0.3rem;
+ font-size: 1.8rem;
+ font-weight: 700;
+ line-height: 1.2;
+ color: #fff;
+}
+
+.msite-header-block__meta {
+ margin: 0 0 0.75rem;
+ font-size: 0.95rem;
+ font-style: italic;
+ color: rgba(255, 255, 255, 0.75);
+}
+
+.msite-header-block__bio {
+ margin-bottom: 0.75rem;
+ font-size: 0.9rem;
+ line-height: 1.5;
+ color: rgba(255, 255, 255, 0.85);
+}
+
+/* --- Contatos ------------------------------------------------------------- */
+
+.msite-header-block__contact {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.3rem 1.5rem;
+ font-size: 0.85rem;
+ color: rgba(255, 255, 255, 0.8);
+}
+
+.msite-header-block__contact-label {
+ font-weight: 600;
+}
+
+.msite-header-block__contact a {
+ color: hsl(202, 79%, 70%);
+ text-decoration: none;
+}
+
+.msite-header-block__contact a:hover {
+ text-decoration: underline;
+}
+
/* Top Bar ------------------------------------------------------------------ */
/*
* Barra estreita no topo da página, antes do header.