Staging deploy 2026/1/21 #2
@ -0,0 +1,39 @@
|
||||
# Generated by Django 5.2.7 on 2026-01-09 05:52
|
||||
|
||||
import django.db.models.deletion
|
||||
import wagtail.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0002_socialmediasettings_alter_navigationsettings_options_and_more'),
|
||||
('wagtailimages', '0027_image_description'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='socialmediasettings',
|
||||
name='links',
|
||||
field=wagtail.fields.StreamField([('link', 2)], block_lookup={0: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('facebook', 'Facebook'), ('twitter', 'Twitter'), ('instagram', 'Instagram'), ('threads', 'Threads'), ('linkedin', 'LinkedIn'), ('youtube', 'YouTube')]}), 1: ('wagtail.blocks.URLBlock', (), {}), 2: ('wagtail.blocks.StructBlock', [[('platform', 0), ('url', 1)]], {})}),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='BannerSnippet',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.CharField(blank=True, help_text='識別用 key(例如 home / category)', max_length=50)),
|
||||
('title', models.CharField(blank=True, max_length=255)),
|
||||
('link_url', models.URLField(blank=True)),
|
||||
('link_text', models.CharField(blank=True, max_length=100)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('sort_order', models.PositiveIntegerField(default=0)),
|
||||
('image', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Banner',
|
||||
'verbose_name_plural': 'Banners',
|
||||
'ordering': ['sort_order', 'id'],
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,29 @@
|
||||
# Generated by Django 5.2.7 on 2026-01-09 09:01
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0003_alter_socialmediasettings_links_bannersnippet'),
|
||||
('wagtailimages', '0027_image_description'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='headersettings',
|
||||
name='logo',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='headersettings',
|
||||
name='logo_dark',
|
||||
field=models.ForeignKey(blank=True, help_text='淺色底用(深色 logo)', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='headersettings',
|
||||
name='logo_light',
|
||||
field=models.ForeignKey(blank=True, help_text='深色底用(亮色 logo)', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image'),
|
||||
),
|
||||
]
|
||||
@ -29,12 +29,21 @@ from wagtail import blocks
|
||||
|
||||
@register_setting
|
||||
class HeaderSettings(BaseGenericSetting):
|
||||
logo = models.ForeignKey(
|
||||
logo_light = models.ForeignKey(
|
||||
"wagtailimages.Image",
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="+",
|
||||
help_text="深色底用(亮色 logo)",
|
||||
)
|
||||
logo_dark = models.ForeignKey(
|
||||
"wagtailimages.Image",
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="+",
|
||||
help_text="淺色底用(深色 logo)",
|
||||
)
|
||||
site_name = models.CharField(max_length=255, blank=True)
|
||||
extra_links = StreamField([
|
||||
@ -47,7 +56,8 @@ class HeaderSettings(BaseGenericSetting):
|
||||
panels = [
|
||||
MultiFieldPanel(
|
||||
[
|
||||
FieldPanel("logo"),
|
||||
FieldPanel("logo_light"),
|
||||
FieldPanel("logo_dark"),
|
||||
FieldPanel("site_name"),
|
||||
FieldPanel("extra_links"),
|
||||
],
|
||||
@ -82,7 +92,7 @@ class SocialLinkBlock(blocks.StructBlock):
|
||||
("facebook", "Facebook"),
|
||||
("twitter", "Twitter"),
|
||||
("instagram", "Instagram"),
|
||||
("thread", "Thread"),
|
||||
("threads", "Threads"),
|
||||
("linkedin", "LinkedIn"),
|
||||
("youtube", "YouTube"),
|
||||
]
|
||||
@ -102,6 +112,40 @@ class SocialMediaSettings(BaseGenericSetting):
|
||||
|
||||
panels = [FieldPanel("links")]
|
||||
|
||||
@register_snippet
|
||||
class BannerSnippet(models.Model):
|
||||
key = models.CharField(max_length=50, blank=True, help_text="識別用 key(例如 home / category)")
|
||||
title = models.CharField(max_length=255, blank=True)
|
||||
image = models.ForeignKey(
|
||||
"wagtailimages.Image",
|
||||
null=True,
|
||||
blank=False,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="+",
|
||||
)
|
||||
link_url = models.URLField(blank=True)
|
||||
link_text = models.CharField(max_length=100, blank=True)
|
||||
is_active = models.BooleanField(default=True)
|
||||
sort_order = models.PositiveIntegerField(default=0)
|
||||
|
||||
panels = [
|
||||
FieldPanel("key"),
|
||||
FieldPanel("title"),
|
||||
FieldPanel("image"),
|
||||
FieldPanel("link_url"),
|
||||
FieldPanel("link_text"),
|
||||
FieldPanel("is_active"),
|
||||
FieldPanel("sort_order"),
|
||||
]
|
||||
|
||||
class Meta:
|
||||
ordering = ["sort_order", "id"]
|
||||
verbose_name = "Banner"
|
||||
verbose_name_plural = "Banners"
|
||||
|
||||
def __str__(self):
|
||||
return self.title or f"Banner {self.pk}"
|
||||
|
||||
@register_snippet
|
||||
class FooterText(
|
||||
DraftStateMixin,
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
{% load wagtailimages_tags banner_tags %}
|
||||
|
||||
{% get_banners banner_key as banners %}
|
||||
{% if banners %}
|
||||
<div class="banner-snippets">
|
||||
{% for banner in banners %}
|
||||
<div class="banner-snippets__item">
|
||||
{% if banner.link_url %}
|
||||
<a class="banner-snippets__link" href="{{ banner.link_url }}">
|
||||
{% image banner.image fill-1200x300 alt=banner.title %}
|
||||
{% if banner.title %}
|
||||
<span class="banner-snippets__title">{{ banner.title }}</span>
|
||||
{% endif %}
|
||||
{% if banner.link_text %}
|
||||
<span class="banner-snippets__cta">{{ banner.link_text }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% else %}
|
||||
{% image banner.image fill-1200x300 alt=banner.title %}
|
||||
{% if banner.title %}
|
||||
<span class="banner-snippets__title">{{ banner.title }}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
20
innovedus_cms/base/templates/base/includes/home_banner.html
Normal file
@ -0,0 +1,20 @@
|
||||
{% load wagtailimages_tags banner_tags %}
|
||||
|
||||
{% get_banners "home" first=True as banner %}
|
||||
{% if banner %}
|
||||
<div class="home-banner">
|
||||
{% if banner.link_url %}
|
||||
<a class="home-banner__link" href="{{ banner.link_url }}">
|
||||
{% image banner.image width-1200 alt=banner.title %}
|
||||
{% if banner.title %}
|
||||
<span class="home-banner__title">{{ banner.title }}</span>
|
||||
{% endif %}
|
||||
{% if banner.link_text %}
|
||||
<span class="home-banner__cta">{{ banner.link_text }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% else %}
|
||||
{% image banner.image width-1200 alt=banner.title %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
15
innovedus_cms/base/templatetags/banner_tags.py
Normal file
@ -0,0 +1,15 @@
|
||||
from django import template
|
||||
|
||||
from base.models import BannerSnippet
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def get_banners(key=None, first=False):
|
||||
banners = BannerSnippet.objects.filter(is_active=True, image__isnull=False)
|
||||
if key:
|
||||
banners = banners.filter(key=key)
|
||||
if first:
|
||||
return banners.first()
|
||||
return banners
|
||||
18
innovedus_cms/home/migrations/0004_articlepage_not_news.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.7 on 2026-01-09 05:52
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0003_categorypage_latestpage_trendingpage_articlepage_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='articlepage',
|
||||
name='not_news',
|
||||
field=models.BooleanField(default=False, help_text='不列入最新消息區塊', verbose_name='Not News'),
|
||||
),
|
||||
]
|
||||
@ -18,8 +18,9 @@ def _get_env_int(name, default):
|
||||
except ValueError:
|
||||
return default
|
||||
|
||||
NEWS_SIZE = _get_env_int("HOMEPAGE_NEWS_SIZE", 8) # Default to 8 articles in news layouts
|
||||
BLOCK_SIZE = _get_env_int("HOMEPAGE_BLOCK_SIZE", 5) # Default to 5 articles in block layout
|
||||
HORIZON_SIZE = _get_env_int("HOMEPAGE_HORIZON_SIZE", 8) # Default to 8 articles in horizon layout
|
||||
HORIZON_SIZE = _get_env_int("HOMEPAGE_HORIZON_SIZE", 4) # Default to 8 articles in horizon layout
|
||||
PAGE_SIZE = _get_env_int("HOMEPAGE_PAGE_SIZE", 10) # Default to 10 articles per page for pagination
|
||||
|
||||
# Mixin for Category-related functionality
|
||||
@ -85,8 +86,8 @@ class CategoryMixin:
|
||||
# No request means no pagination (e.g., homepage)
|
||||
return {
|
||||
"title": latest_page.title,
|
||||
"items": ArticlePage.objects.live().order_by("-date")[
|
||||
:BLOCK_SIZE
|
||||
"items": ArticlePage.objects.filter(not_news=False).live().order_by("-date")[
|
||||
:NEWS_SIZE
|
||||
],
|
||||
"url": latest_page.url,
|
||||
}
|
||||
@ -154,21 +155,24 @@ class HomePage(Page, CategoryMixin):
|
||||
}
|
||||
|
||||
latest_section = self.get_latest_articles().copy()
|
||||
latest_section["layout"] = "block"
|
||||
# latest_section["layout"] = "block"
|
||||
sections["top_section"].append(latest_section)
|
||||
|
||||
# NOT NEED TRENDING SECTION CURRENTLY
|
||||
# ------------------------------------------------------------------
|
||||
# Exclude latest articles from trending section
|
||||
latest_items = latest_section.get("items", [])
|
||||
if hasattr(latest_items, "values_list"):
|
||||
latest_ids = list(latest_items.values_list("id", flat=True))
|
||||
else:
|
||||
latest_ids = [item.id for item in latest_items]
|
||||
# latest_items = latest_section.get("items", [])
|
||||
# if hasattr(latest_items, "values_list"):
|
||||
# latest_ids = list(latest_items.values_list("id", flat=True))
|
||||
# else:
|
||||
# latest_ids = [item.id for item in latest_items]
|
||||
|
||||
trending_section = self.get_trending_articles(
|
||||
exclude_ids=latest_ids
|
||||
).copy()
|
||||
trending_section["layout"] = "horizon"
|
||||
sections["top_section"].append(trending_section)
|
||||
# trending_section = self.get_trending_articles(
|
||||
# exclude_ids=latest_ids
|
||||
# ).copy()
|
||||
# trending_section["layout"] = "horizon"
|
||||
# sections["top_section"].append(trending_section)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
# Build category sections
|
||||
categories = CategoryPage.objects.child_of(self).live().in_menu()
|
||||
@ -271,6 +275,7 @@ class ArticlePage(Page):
|
||||
use_json_field=True,
|
||||
)
|
||||
trending = models.BooleanField("Trending", default=False, help_text="在熱門區塊顯示")
|
||||
not_news = models.BooleanField("Not News", default=False, help_text="不列入最新消息區塊")
|
||||
tags = ClusterTaggableManager(through="home.ArticlePageTag", blank=True)
|
||||
|
||||
search_fields = Page.search_fields + [
|
||||
@ -281,6 +286,7 @@ class ArticlePage(Page):
|
||||
|
||||
content_panels = Page.content_panels + [
|
||||
FieldPanel("trending"),
|
||||
FieldPanel("not_news"),
|
||||
FieldPanel("cover_image"),
|
||||
FieldPanel("banner_image"),
|
||||
FieldPanel("date"),
|
||||
|
||||
79
innovedus_cms/home/static/css/block_list.css
Normal file
@ -0,0 +1,79 @@
|
||||
.block-list-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.block-list-arrow {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.18);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.block-list-arrow[data-dir="left"] {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.block-list-arrow[data-dir="right"] {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.block-list-arrow.is-hidden {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.block-list {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 20px;
|
||||
overflow-x: auto;
|
||||
scroll-snap-type: x mandatory;
|
||||
list-style: none;
|
||||
margin: 24px 0;
|
||||
padding: 12px 48px;
|
||||
}
|
||||
|
||||
.block-list li {
|
||||
flex: 0 0 200px;
|
||||
overflow: hidden;
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
|
||||
.block-list li a {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.block-list li img {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
/* object-fit: contain; */
|
||||
display: block;
|
||||
/* background: #f5f5f5; */
|
||||
/* border-radius: 8px; */
|
||||
}
|
||||
|
||||
.block-list li a:hover,
|
||||
.block-list li a:focus {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.block-list .empty {
|
||||
flex: 1 0 auto;
|
||||
text-align: center;
|
||||
padding: 24px;
|
||||
color: #666;
|
||||
background: #fafafa;
|
||||
border: 1px dashed #ddd;
|
||||
border-radius: 12px;
|
||||
}
|
||||
74
innovedus_cms/home/static/css/home.css
Normal file
@ -0,0 +1,74 @@
|
||||
.home-page {
|
||||
max-width: 890px;
|
||||
margin: 0 auto;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.home-hero-band {
|
||||
background-color: #0e1b42;
|
||||
color: #ffffff;
|
||||
padding-bottom: 86px;
|
||||
}
|
||||
|
||||
.home-hero-band a {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.template-homepage .site-header {
|
||||
background-color: #0e1b42;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.template-homepage .header-inner {
|
||||
max-width: 1028px;
|
||||
margin: 0 auto;
|
||||
/* padding: 0 16px; */
|
||||
}
|
||||
|
||||
.template-homepage .main-menu-link {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.template-homepage .header-search .search-input {
|
||||
/* border-color: #ffffff; */
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.template-homepage .header-search .search-icon {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.template-homepage .header-search input[type="search"] {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.list-title {
|
||||
align-items: center;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.block-title {
|
||||
display: inline-block;
|
||||
width: 197px;
|
||||
height: 87px;
|
||||
vertical-align: middle;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.block-title span {
|
||||
padding-left: 21px;
|
||||
line-height: 87px;
|
||||
}
|
||||
|
||||
.block-title-divider {
|
||||
display: inline-flex;
|
||||
width: 28px;
|
||||
height: 1px;
|
||||
transform: translate(-4px, -4px);
|
||||
}
|
||||
|
||||
.more-link {
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
}
|
||||
112
innovedus_cms/home/static/css/horizontal_list.css
Normal file
@ -0,0 +1,112 @@
|
||||
.horizontal-list-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.horizontal-list-arrow {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.18);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.horizontal-list-arrow[data-dir="left"] {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.horizontal-list-arrow[data-dir="right"] {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.horizontal-list-arrow-icon {
|
||||
width: 18px;
|
||||
height: 34px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.horizontal-list-arrow[data-dir="right"] .horizontal-list-arrow-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.horizontal-list-arrow.is-hidden {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.horizontal-list {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 24px;
|
||||
overflow-x: auto;
|
||||
scroll-snap-type: x mandatory;
|
||||
list-style: none;
|
||||
margin: 24px 0;
|
||||
padding: 12px 48px;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.horizontal-list::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.horizontal-list li {
|
||||
flex: 0 0 200px;
|
||||
overflow: hidden;
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
|
||||
.horizontal-list li a {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.horizontal-list li img {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
display: block;
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
.horizontal-list li a:hover,
|
||||
.horizontal-list li a:focus {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.horizontal-list li a span {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.horizontal-list .article-title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.horizontal-list .article-intro {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.horizontal-list .empty {
|
||||
flex: 1 0 auto;
|
||||
text-align: center;
|
||||
padding: 24px;
|
||||
color: #666;
|
||||
background: #fafafa;
|
||||
border: 1px dashed #ddd;
|
||||
border-radius: 12px;
|
||||
}
|
||||
182
innovedus_cms/home/static/css/news_list.css
Normal file
@ -0,0 +1,182 @@
|
||||
.news-title, .more-news-title {
|
||||
background-color: #ffffff;
|
||||
color: #0e1b42;
|
||||
}
|
||||
|
||||
.news-hero {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-areas:
|
||||
"header header"
|
||||
"image meta"
|
||||
"image intro"
|
||||
"image body";
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.news-hero .news-hero-header {
|
||||
grid-area: header;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.news-hero .list-title {
|
||||
grid-area: title;
|
||||
max-width: 310px;
|
||||
flex: 0 1 310px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.news-hero .fist-news-title {
|
||||
grid-area: head;
|
||||
max-width: 580px;
|
||||
font-size: 40px;
|
||||
font-weight: 400;
|
||||
color: #eb9f13;
|
||||
margin: 0;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.news-hero .first-news-image {
|
||||
grid-area: image;
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.news-hero .first-news-image::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
height: 25px;
|
||||
background: url("../img/picfrm_b480.png") no-repeat left bottom / cover;
|
||||
background-size: 480px 25px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.news-hero .first-news-image img {
|
||||
display: block;
|
||||
width:480px;
|
||||
height:293px;
|
||||
object-fit:cover;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.news-hero .fist-news-date {
|
||||
grid-area: meta;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.news-hero .first-news-intro {
|
||||
grid-area: intro;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.news-hero .first-news-body {
|
||||
grid-area: body;
|
||||
font: 12px;
|
||||
}
|
||||
|
||||
.news-list-items {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.news-list-lower {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.news-list-items a {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
width: 200px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.news-list-thumb {
|
||||
position: relative;
|
||||
width: 194px;
|
||||
height: 133px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.news-list-thumb::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
height: 25px;
|
||||
background: url("../img/picfrm_o194.png") no-repeat left bottom / cover;
|
||||
background-size: 194px 25px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.news-list-thumb img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.news-list-items a span{
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.news-list-items .article-title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.news-list-items .article-date {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.more-news {
|
||||
width: 200px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.more-news .article-title {
|
||||
font-size: 20px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.news-hero {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-areas:
|
||||
"title"
|
||||
"image"
|
||||
"head"
|
||||
"meta"
|
||||
"intro"
|
||||
"body";
|
||||
}
|
||||
|
||||
.news-hero .news-hero-header {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.news-hero .list-title {
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
BIN
innovedus_cms/home/static/img/picfrm_b139.png
Normal file
|
After Width: | Height: | Size: 926 B |
BIN
innovedus_cms/home/static/img/picfrm_b194.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
innovedus_cms/home/static/img/picfrm_b300.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
innovedus_cms/home/static/img/picfrm_b318.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
innovedus_cms/home/static/img/picfrm_b426.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
innovedus_cms/home/static/img/picfrm_b480.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
innovedus_cms/home/static/img/picfrm_o139.png
Normal file
|
After Width: | Height: | Size: 938 B |
BIN
innovedus_cms/home/static/img/picfrm_o194.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
innovedus_cms/home/static/img/picfrm_o300.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
innovedus_cms/home/static/img/picfrm_o318.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
innovedus_cms/home/static/img/picfrm_o426.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
innovedus_cms/home/static/img/picfrm_o480.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
45
innovedus_cms/home/static/js/block_list.js
Normal file
@ -0,0 +1,45 @@
|
||||
(function () {
|
||||
function initBlockList(block) {
|
||||
var list = block.querySelector('.block-list');
|
||||
var left = block.querySelector('[data-dir="left"]');
|
||||
var right = block.querySelector('[data-dir="right"]');
|
||||
|
||||
if (!list || !left || !right) {
|
||||
return;
|
||||
}
|
||||
|
||||
function getScrollAmount() {
|
||||
return Math.max(200, Math.floor(list.clientWidth * 0.8));
|
||||
}
|
||||
|
||||
function updateArrows() {
|
||||
var maxScroll = list.scrollWidth - list.clientWidth;
|
||||
var hasOverflow = maxScroll > 1;
|
||||
var atStart = list.scrollLeft <= 1;
|
||||
var atEnd = list.scrollLeft >= maxScroll - 1;
|
||||
|
||||
left.classList.toggle('is-hidden', !hasOverflow || atStart);
|
||||
right.classList.toggle('is-hidden', !hasOverflow || atEnd);
|
||||
}
|
||||
|
||||
left.addEventListener('click', function () {
|
||||
list.scrollBy({ left: -getScrollAmount(), behavior: 'smooth' });
|
||||
});
|
||||
|
||||
right.addEventListener('click', function () {
|
||||
list.scrollBy({ left: getScrollAmount(), behavior: 'smooth' });
|
||||
});
|
||||
|
||||
list.addEventListener('scroll', function () {
|
||||
window.requestAnimationFrame(updateArrows);
|
||||
});
|
||||
|
||||
window.addEventListener('resize', updateArrows);
|
||||
updateArrows();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var blocks = document.querySelectorAll('[data-block-list]');
|
||||
blocks.forEach(initBlockList);
|
||||
});
|
||||
})();
|
||||
45
innovedus_cms/home/static/js/horizontal_list.js
Normal file
@ -0,0 +1,45 @@
|
||||
(function () {
|
||||
function initHorizontalList(block) {
|
||||
var list = block.querySelector('.horizontal-list');
|
||||
var left = block.querySelector('[data-dir="left"]');
|
||||
var right = block.querySelector('[data-dir="right"]');
|
||||
|
||||
if (!list || !left || !right) {
|
||||
return;
|
||||
}
|
||||
|
||||
function getScrollAmount() {
|
||||
return Math.max(200, Math.floor(list.clientWidth * 0.8));
|
||||
}
|
||||
|
||||
function updateArrows() {
|
||||
var maxScroll = list.scrollWidth - list.clientWidth;
|
||||
var hasOverflow = maxScroll > 1;
|
||||
var atStart = list.scrollLeft <= 1;
|
||||
var atEnd = list.scrollLeft >= maxScroll - 1;
|
||||
|
||||
left.classList.toggle('is-hidden', !hasOverflow || atStart);
|
||||
right.classList.toggle('is-hidden', !hasOverflow || atEnd);
|
||||
}
|
||||
|
||||
left.addEventListener('click', function () {
|
||||
list.scrollBy({ left: -getScrollAmount(), behavior: 'smooth' });
|
||||
});
|
||||
|
||||
right.addEventListener('click', function () {
|
||||
list.scrollBy({ left: getScrollAmount(), behavior: 'smooth' });
|
||||
});
|
||||
|
||||
list.addEventListener('scroll', function () {
|
||||
window.requestAnimationFrame(updateArrows);
|
||||
});
|
||||
|
||||
window.addEventListener('resize', updateArrows);
|
||||
updateArrows();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var blocks = document.querySelectorAll('[data-horizontal-list]');
|
||||
blocks.forEach(initHorizontalList);
|
||||
});
|
||||
})();
|
||||
@ -1,20 +1,33 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% block body_class %}template-homepage{% endblock %}
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'css/home.css' %}">
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'css/news_list.css' %}">
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'css/block_list.css' %}">
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'css/horizontal_list.css' %}">
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="home-hero-band">
|
||||
<div class="home-page">
|
||||
<div class="home-hero">
|
||||
{% include "base/includes/home_banner.html" %}
|
||||
{% with top_section=sections.top_section %}
|
||||
<h2>
|
||||
<a href="{{ top_section.0.url }}">最新文章</a>
|
||||
</h2>
|
||||
{% for section in top_section %}
|
||||
{% if section.layout == "block" %}
|
||||
{% include "home/includes/block_list.html" with items=section.items %}
|
||||
{% elif section.layout == "horizon" %}
|
||||
{% include "home/includes/horizontal_list.html" with items=section.items %}
|
||||
{% endif %}
|
||||
{% include "home/includes/news_list.html" with section=section %}
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="home-page">
|
||||
{% for section in sections.category_sections %}
|
||||
{% include "home/includes/category_session.html" with section=section %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
{% block extra_js %}
|
||||
<script type="text/javascript" src="{% static 'js/block_list.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'js/horizontal_list.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
{% load wagtailimages_tags static %}
|
||||
|
||||
<div class="block-list-wrap" data-block-list>
|
||||
<button class="block-list-arrow" type="button" data-dir="left" aria-label="上一頁">‹</button>
|
||||
<ul class="block-list">
|
||||
{% for article in items %}
|
||||
<li>
|
||||
@ -17,3 +19,5 @@
|
||||
<li class="empty">目前沒有文章</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<button class="block-list-arrow" type="button" data-dir="right" aria-label="下一頁">›</button>
|
||||
</div>
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
{% load wagtailimages_tags static %}
|
||||
|
||||
<section class="article-section article-section--{{ section.layout }}">
|
||||
<h2>
|
||||
{% if section.url %}
|
||||
<a href="{{ section.url }}">{{ section.title }}</a>
|
||||
{% else %}
|
||||
{{ section.title }}
|
||||
{% endif %}
|
||||
</h2>
|
||||
<div class="list-title">
|
||||
<div class="block-title category_title"><span>{{ section.title }}</span></div>
|
||||
<span class="block-title-divider" aria-hidden="true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" height="100%" overflow="visible" preserveAspectRatio="none" viewBox="0 0 28 1" width="100%">
|
||||
<line stroke="currentColor" x2="28" y1="0.5" y2="0.5"/>
|
||||
</svg>
|
||||
</span>
|
||||
<a href="{{ section.url }}"><span class="more-link">查看全部</span></a>
|
||||
</div>
|
||||
{% if section.layout == "block" %}
|
||||
{% include "home/includes/block_list.html" with items=section.items %}
|
||||
{% elif section.layout == "horizon" %}
|
||||
|
||||
@ -1,19 +1,32 @@
|
||||
{% load wagtailimages_tags static %}
|
||||
|
||||
<div class="horizontal-list-wrap" data-horizontal-list>
|
||||
<button class="horizontal-list-arrow is-hidden" type="button" data-dir="left" aria-label="上一頁">
|
||||
<svg class="horizontal-list-arrow-icon" xmlns="http://www.w3.org/2000/svg" fill="none" overflow="visible" preserveAspectRatio="none" viewBox="0 0 18.1213 33.4142">
|
||||
<path d="M17.4142 0.707107L1.41421 16.7071L17.4142 32.7071" id="Vector 3" stroke="var(--stroke-0, #0E1B42)" stroke-width="2"/>
|
||||
</svg>
|
||||
</button>
|
||||
<ul class="horizontal-list">
|
||||
{% for article in items %}
|
||||
<li>
|
||||
<a href="{{ article.url }}">
|
||||
{% if article.cover_image %}
|
||||
{% image article.cover_image max-200x200 as cover %}
|
||||
<img src="{{ cover.url }}" alt="{{ article.title }}" height="200" width="200" style="width:200px;height:200px;object-fit:cover;display:block;"/>
|
||||
{% image article.cover_image max-194x133 as cover %}
|
||||
<img src="{{ cover.url }}" alt="{{ article.title }}" height="133" width="194" style="width:194px;height:133px;object-fit:cover;display:block;"/>
|
||||
{% else %}
|
||||
<img src="{% static 'img/default_cover.jpg' %}" alt="{{ article.title }}" height="200" width="200" style="width:200px;height:200px;object-fit:cover;display:block;"/>
|
||||
<img src="{% static 'img/default_cover.jpg' %}" alt="{{ article.title }}" height="133" width="194" style="width:194px;height:133px;object-fit:cover;display:block;"/>
|
||||
{% endif %}
|
||||
<span>{{ article.title }}</span>
|
||||
<span class="article-title">{{ article.title }}</span>
|
||||
<span class="article-intro">{{ article.intro }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% empty %}
|
||||
<li class="empty">目前沒有文章</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<button class="horizontal-list-arrow" type="button" data-dir="right" aria-label="下一頁">
|
||||
<svg class="horizontal-list-arrow-icon" xmlns="http://www.w3.org/2000/svg" fill="none" overflow="visible" preserveAspectRatio="none" viewBox="0 0 18.1213 33.4142">
|
||||
<path d="M17.4142 0.707107L1.41421 16.7071L17.4142 32.7071" id="Vector 3" stroke="var(--stroke-0, #0E1B42)" stroke-width="2"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
82
innovedus_cms/home/templates/home/includes/news_list.html
Normal file
@ -0,0 +1,82 @@
|
||||
{% load wagtailimages_tags static %}
|
||||
|
||||
<div class="news-list-wrap" data-news-list>
|
||||
<div class="news-hero">
|
||||
<div class="news-hero-header">
|
||||
<div class="list-title">
|
||||
<div class="block-title news-title"><span>本日頭條</span></div>
|
||||
<span class="block-title-divider" aria-hidden="true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" height="100%" overflow="visible" preserveAspectRatio="none" viewBox="0 0 28 1" width="100%">
|
||||
<line stroke="currentColor" x2="28" y1="0.5" y2="0.5"/>
|
||||
</svg>
|
||||
</span>
|
||||
<a href="{{ section.url }}"><span class="more-link">查看全部</span></a>
|
||||
</div>
|
||||
{% with first_article=section.items|first %}
|
||||
{% if first_article %}
|
||||
<div class="fist-news-title">
|
||||
<span>{{ first_article.title }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% with first_article=section.items|first %}
|
||||
{% if first_article %}
|
||||
<div class="fist-news-date">
|
||||
<span>{{ first_article.date|date:"Y.m.d" }}</span>
|
||||
</div>
|
||||
<div class="first-news-image">
|
||||
<a href="{{ first_article.url }}">
|
||||
{% if first_article.cover_image %}
|
||||
{% image first_article.cover_image max-480x320 as cover %}
|
||||
<img src="{{ cover.url }}" alt="{{ first_article.title }}" height="293" width="480"/>
|
||||
{% else %}
|
||||
<img src="{% static 'img/default_cover.jpg' %}" alt="{{ first_article.title }}" height="293" width="480"/>
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
{% if first_article.intro %}
|
||||
<div class="first-news-intro">
|
||||
<span>{{ first_article.intro }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="first-news-body">
|
||||
<span>{{ first_article.body_search_text|truncatechars:120 }}</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="empty">目前沒有文章</span>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
{% if section.items|length >= 2 %}
|
||||
<div class="news-list-lower">
|
||||
<div class="news-list-items">
|
||||
{% for article in section.items|slice:"1:4" %}
|
||||
<a href="{{ article.url }}">
|
||||
<div class="news-list-thumb">
|
||||
{% if article.cover_image %}
|
||||
{% image article.cover_image max-194x133 as cover %}
|
||||
<img src="{{ cover.url }}" alt="{{ article.title }}" height="133" width="194"/>
|
||||
{% else %}
|
||||
<img src="{% static 'img/default_cover.jpg' %}" alt="{{ article.title }}" height="133" width="194"/>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div><span class="article-title">{{ article.title }}</span></div>
|
||||
<div><span class="article-intro">{{ article.intro }}</span></div>
|
||||
<div><span class="article-date">{{ article.date|date:"Y.m.d" }}</span></div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="more-news">
|
||||
<div class="block-title more-news-title"><span>更多頭條</span></div>
|
||||
{% for article in section.items|slice:"4:8" %}
|
||||
<a href="{{ article.url }}">
|
||||
<span class="article-title">{{ article.title }}</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -0,0 +1,288 @@
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.site-header {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.header-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
padding: 30px 105px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo--light {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.logo--dark {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.template-homepage .logo--light {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.template-homepage .logo--dark {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.main-nav {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.main-menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.main-menu-link {
|
||||
display: inline-block;
|
||||
padding: 12px 4px;
|
||||
font-variation-settings: normal;
|
||||
color: #0e1b42;
|
||||
font-family: "Inter:Regular", "Noto Sans JP:Regular", sans-serif;
|
||||
word-break: break-word;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-size: 14px;
|
||||
letter-spacing: 0px;
|
||||
line-height: normal;
|
||||
--letter-spacing: 0px;
|
||||
}
|
||||
|
||||
.submenu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
margin-top: -2px;
|
||||
min-width: 220px;
|
||||
list-style: none;
|
||||
padding: 14px 0;
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.18);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
transition: opacity 160ms ease, transform 160ms ease;
|
||||
}
|
||||
|
||||
.menu-item:hover .submenu,
|
||||
.menu-item:focus-within .submenu {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
transform: translateX(-50%) translateY(2px);
|
||||
}
|
||||
|
||||
.submenu-item a {
|
||||
display: block;
|
||||
font-variation-settings: normal;
|
||||
color: #0e1b42;
|
||||
font-family: "Inter:Regular", "Noto Sans JP:Regular", sans-serif;
|
||||
word-break: break-word;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-size: 14px;
|
||||
letter-spacing: 0px;
|
||||
line-height: normal;
|
||||
--letter-spacing: 0px;
|
||||
padding: 10px 18px;
|
||||
}
|
||||
|
||||
.submenu-item a:hover,
|
||||
.submenu-item a:focus {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
|
||||
.header-search .search-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-style: solid;
|
||||
/* border-color: #0e1b42; */
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.header-search .search-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: none;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
color: #1b2140; /* icon 顏色 */
|
||||
}
|
||||
|
||||
.header-search .search-icon svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.header-search input[type="search"] {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
color: #fff;
|
||||
width: 160px; /* 依需要調 */
|
||||
}
|
||||
|
||||
@layer figreset {
|
||||
:root {
|
||||
font-family: var( --default-font-family,ui-sans-serif,system-ui,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol','Noto Color Emoji' )
|
||||
}
|
||||
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
body: has([data-page-overflowx='hidden']) {
|
||||
overflow-x:hidden
|
||||
}
|
||||
|
||||
body: has([data-page-overflowx='auto']) {
|
||||
overflow-x:auto
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
background: #0e1b42;
|
||||
color: #ffffff;
|
||||
padding: 24px 0;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 48px;
|
||||
align-items: flex-start;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
footer .company-info {
|
||||
max-width: 300px;
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
footer .copyright img {
|
||||
margin: 10px 0;
|
||||
width: 265px;
|
||||
height: 37px;
|
||||
}
|
||||
|
||||
footer .copyright p {
|
||||
font-size: 10px;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.footer-socials {
|
||||
display: flex;
|
||||
/* justify-content: center; */
|
||||
gap: 12px;
|
||||
margin: 16px 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.footer-socials .icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
--fill-0: #ffffff;
|
||||
}
|
||||
|
||||
.footer-socials .icon circle {
|
||||
fill: #ffffff;
|
||||
fill-opacity: 0.85;
|
||||
}
|
||||
|
||||
.footer-socials .icon .icon-cutout {
|
||||
fill: #0e1b42;
|
||||
}
|
||||
|
||||
footer .footer-links {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
footer .footer-links li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
footer .footer-links a {
|
||||
font-size: 14px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
footer .footer-divider {
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 8px;
|
||||
width: 1px;
|
||||
border-left: 1px solid #ffffff;
|
||||
}
|
||||
|
||||
footer .footer-sections {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
footer {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
footer .company-info,
|
||||
footer .footer-links {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.footer-socials {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
footer .footer-sections {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
footer .footer-divider {
|
||||
width: 100%;
|
||||
padding: 12px 0;
|
||||
border-left: 0;
|
||||
border-top: 1px solid #ffffff;
|
||||
}
|
||||
|
||||
footer .copyright p {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,58 @@
|
||||
{% load navigation_tags %}
|
||||
|
||||
<footer>
|
||||
<div class="company-info">
|
||||
<div class="copyright">
|
||||
{% get_footer_text %}
|
||||
</div>
|
||||
{% with social_links=settings.base.SocialMediaSettings.links %}
|
||||
{% if social_links %}
|
||||
<div class="footer-socials" aria-label="social icons">
|
||||
{% for item in social_links %}
|
||||
<a href="{{ item.value.url }}" target="_blank" aria-label="{{ item.value.platform }}">
|
||||
{% if item.value.platform|lower == "facebook" %}
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 33.18 33.18" aria-label="facebook" role="img">
|
||||
<path d="M16.59 0C7.43 0 0 7.43 0 16.59C0 25.75 7.43 33.18 16.59 33.18C25.75 33.18 33.18 25.75 33.18 16.59C33.18 7.43 25.75 0 16.59 0ZM23.12 8.37H21.18C19.27 8.37 18.67 9.56 18.67 10.78V13.67H22.94L22.26 18.12H18.67V28.89H13.85V18.12H9.94V13.67H13.85V10.28C13.85 6.42 16.15 4.29 19.67 4.29C21.36 4.29 23.12 4.59 23.12 4.59V8.38V8.37Z" fill="var(--fill-0, #0E1B42)" id="Vector"/>
|
||||
</svg>
|
||||
{% elif item.value.platform|lower == "instagram" %}
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 33.18 33.18" aria-label="instagram" role="img">
|
||||
<path d="M16.67 13.42C14.92 13.42 13.5 14.84 13.5 16.59C13.5 18.34 14.92 19.76 16.67 19.76C18.42 19.76 19.84 18.34 19.84 16.59C19.84 14.84 18.42 13.42 16.67 13.42Z" fill="var(--fill-0, #0E1B42)" id="Vector"/>
|
||||
<path d="M20.6 8.42H12.57C10.28 8.42 8.42 10.28 8.42 12.57V20.6C8.42 22.89 10.28 24.75 12.57 24.75H20.6C22.89 24.75 24.75 22.89 24.75 20.6V12.57C24.75 10.28 22.89 8.42 20.6 8.42ZM16.67 21.54C13.94 21.54 11.72 19.32 11.72 16.59C11.72 13.86 13.94 11.64 16.67 11.64C19.4 11.64 21.62 13.86 21.62 16.59C21.62 19.32 19.4 21.54 16.67 21.54ZM21.91 12.48C21.26 12.48 20.73 11.95 20.73 11.3C20.73 10.65 21.26 10.12 21.91 10.12C22.56 10.12 23.09 10.65 23.09 11.3C23.09 11.95 22.56 12.48 21.91 12.48Z" fill="var(--fill-0, #0E1B42)" id="Vector_2"/>
|
||||
<path d="M16.59 0C7.43 0 0 7.43 0 16.59C0 25.75 7.43 33.18 16.59 33.18C25.75 33.18 33.18 25.75 33.18 16.59C33.18 7.43 25.75 0 16.59 0ZM26.64 20.6C26.64 23.93 23.93 26.64 20.6 26.64H12.57C9.24 26.64 6.53 23.93 6.53 20.6V12.57C6.53 9.24 9.24 6.53 12.57 6.53H20.6C23.93 6.53 26.64 9.24 26.64 12.57V20.6Z" fill="var(--fill-0, #0E1B42)" id="Vector_3"/>
|
||||
</svg>
|
||||
{% elif item.value.platform|lower == "youtube" %}
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 33.18 33.18" aria-label="youtube" role="img">
|
||||
<path d="M20.36 16.45L15.14 13.61C14.93 13.49 14.19 13.64 14.19 13.89V19.43C14.19 19.67 14.92 19.83 15.13 19.71L20.59 17.01C20.81 16.89 20.59 16.57 20.36 16.45Z" fill="var(--fill-0, #0E1B42)" id="Vector"/>
|
||||
<path d="M16.59 0C7.43 0 0 7.43 0 16.59C0 25.75 7.43 33.18 16.59 33.18C25.75 33.18 33.18 25.75 33.18 16.59C33.18 7.43 25.75 0 16.59 0ZM27.74 19.42C27.74 22.05 25.61 24.18 22.98 24.18H10.81C8.18 24.18 6.05 22.05 6.05 19.42V13.76C6.05 11.13 8.18 9 10.81 9H22.98C25.61 9 27.74 11.13 27.74 13.76V19.42Z" fill="var(--fill-0, #0E1B42)" id="Vector_2"/>
|
||||
</svg>
|
||||
{% elif item.value.platform|lower == "threads" %}
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 33.18 33.18" aria-label="threads" role="img">
|
||||
<path d="M16.59 0C7.43 0 0 7.43 0 16.59C0 25.75 7.43 33.18 16.59 33.18C25.75 33.18 33.18 25.75 33.18 16.59C33.18 7.43 25.75 0 16.59 0Z" fill="var(--fill-0, #0E1B42)"/>
|
||||
<path class="icon-cutout" d="M6.321 6.016c-.27-.18-1.166-.802-1.166-.802.756-1.081 1.753-1.502 3.132-1.502.975 0 1.803.327 2.394.948s.928 1.509 1.005 2.644q.492.207.905.484c1.109.745 1.719 1.86 1.719 3.137 0 2.716-2.226 5.075-6.256 5.075C4.594 16 1 13.987 1 7.994 1 2.034 4.482 0 8.044 0 9.69 0 13.55.243 15 5.036l-1.36.353C12.516 1.974 10.163 1.43 8.006 1.43c-3.565 0-5.582 2.171-5.582 6.79 0 4.143 2.254 6.343 5.63 6.343 2.777 0 4.847-1.443 4.847-3.556 0-1.438-1.208-2.127-1.27-2.127-.236 1.234-.868 3.31-3.644 3.31-1.618 0-3.013-1.118-3.013-2.582 0-2.09 1.984-2.847 3.55-2.847.586 0 1.294.04 1.663.114 0-.637-.54-1.728-1.9-1.728-1.25 0-1.566.405-1.967.868ZM8.716 8.19c-2.04 0-2.304.87-2.304 1.416 0 .878 1.043 1.168 1.6 1.168 1.02 0 2.067-.282 2.232-2.423a6.2 6.2 0 0 0-1.528-.161" transform="translate(7.59 7.59) scale(1.125)"/>
|
||||
</svg>
|
||||
{% elif item.value.platform|lower == "linkedin" %}
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 33.18 33.18" aria-label="linkedin" role="img">
|
||||
<path d="M16.59 0C7.43 0 0 7.43 0 16.59C0 25.75 7.43 33.18 16.59 33.18C25.75 33.18 33.18 25.75 33.18 16.59C33.18 7.43 25.75 0 16.59 0Z" fill="var(--fill-0, #0E1B42)"/>
|
||||
<path class="icon-cutout" d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z" transform="translate(7.59 7.59) scale(1.125)"/>
|
||||
</svg>
|
||||
{% elif item.value.platform|lower == "x" or item.value.platform|lower == "twitter" or item.value.platform|lower == "twitter-x" %}
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 33.18 33.18" aria-label="twitter-x" role="img">
|
||||
<path d="M16.59 0C7.43 0 0 7.43 0 16.59C0 25.75 7.43 33.18 16.59 33.18C25.75 33.18 33.18 25.75 33.18 16.59C33.18 7.43 25.75 0 16.59 0Z" fill="var(--fill-0, #0E1B42)"/>
|
||||
<path class="icon-cutout" d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865z" transform="translate(7.59 7.59) scale(1.125)"/>
|
||||
</svg>
|
||||
{% else %}
|
||||
{{ item.value.platform }}
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
<div class="footer-divider" aria-hidden="true"></div>
|
||||
|
||||
<div class="footer-links">
|
||||
{% if settings.base.NavigationSettings.footer_links %}
|
||||
<div class="footer-sections">
|
||||
{% for section in settings.base.NavigationSettings.footer_links %}
|
||||
@ -17,16 +69,5 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% with social_links=settings.base.SocialMediaSettings.links %}
|
||||
{% if social_links %}
|
||||
<p>Follow us
|
||||
{% for item in social_links %}
|
||||
<a href="{{ item.value.url }}" target="_blank" alt="{{ item.value.platform }}">{{ item.value.platform }}</a>
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% get_footer_text %}
|
||||
</div>
|
||||
</footer>
|
||||
@ -1,45 +1,83 @@
|
||||
{% load wagtailsettings_tags wagtailimages_tags %}
|
||||
{% get_settings use_default_site=True as settings %}
|
||||
|
||||
<header class="site-header">
|
||||
<header class="site-header{% if settings.base.HeaderSettings.logo_light and settings.base.HeaderSettings.logo_dark %} has-logo-variants{% endif %}">
|
||||
<div class="header-inner">
|
||||
{% if settings.base.HeaderSettings.logo %}
|
||||
<a href="/" class="logo">
|
||||
{% image settings.base.HeaderSettings.logo fill-60x60 %}
|
||||
{% if settings.base.HeaderSettings.logo_light and settings.base.HeaderSettings.logo_dark %}
|
||||
<a href="/" class="logo logo--light">
|
||||
{% if settings.base.HeaderSettings.site_name %}
|
||||
<span class="site-name">{{ settings.base.HeaderSettings.site_name }}</span>
|
||||
{% image settings.base.HeaderSettings.logo_light fill-197x28 alt=settings.base.HeaderSettings.site_name %}
|
||||
{% else %}
|
||||
{% image settings.base.HeaderSettings.logo_light fill-197x28 %}
|
||||
{% endif %}
|
||||
</a>
|
||||
<a href="/" class="logo logo--dark">
|
||||
{% if settings.base.HeaderSettings.site_name %}
|
||||
{% image settings.base.HeaderSettings.logo_dark fill-197x28 alt=settings.base.HeaderSettings.site_name %}
|
||||
{% else %}
|
||||
{% image settings.base.HeaderSettings.logo_dark fill-197x28 %}
|
||||
{% endif %}
|
||||
</a>
|
||||
{% elif settings.base.HeaderSettings.logo_light %}
|
||||
<a href="/" class="logo">
|
||||
{% if settings.base.HeaderSettings.site_name %}
|
||||
{% image settings.base.HeaderSettings.logo_light fill-197x28 alt=settings.base.HeaderSettings.site_name %}
|
||||
{% else %}
|
||||
{% image settings.base.HeaderSettings.logo_light fill-197x28 %}
|
||||
{% endif %}
|
||||
</a>
|
||||
{% elif settings.base.HeaderSettings.logo_dark %}
|
||||
<a href="/" class="logo">
|
||||
{% if settings.base.HeaderSettings.site_name %}
|
||||
{% image settings.base.HeaderSettings.logo_dark fill-197x28 alt=settings.base.HeaderSettings.site_name %}
|
||||
{% else %}
|
||||
{% image settings.base.HeaderSettings.logo_dark fill-197x28 %}
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<nav class="main-nav">
|
||||
<ul>
|
||||
<ul class="main-menu">
|
||||
{% with site_root=page.get_site.root_page %}
|
||||
{# Top-level menu: direct children of site root #}
|
||||
<li>
|
||||
<li class="menu-item">
|
||||
<a href="#">
|
||||
最新文章
|
||||
<span class="main-menu-link">最新文章</span>
|
||||
</a>
|
||||
{% if nav_latest_page or nav_trending_page %}
|
||||
<ul class="submenu">
|
||||
{% if nav_latest_page %}
|
||||
<li><a href="{{ nav_latest_page.url }}">{{ nav_latest_page.title }}</a></li>
|
||||
<li class="submenu-item">
|
||||
<a href="{{ nav_latest_page.url }}">
|
||||
<span class="submenu-item-link">{{ nav_latest_page.title }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if nav_trending_page %}
|
||||
<li><a href="{{ nav_trending_page.url }}">{{ nav_trending_page.title }}</a></li>
|
||||
<li class="submenu-item">
|
||||
<a href="{{ nav_trending_page.url }}">
|
||||
<span class="submenu-item-link">{{ nav_trending_page.title }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% for menu_page in site_root.get_children.live.in_menu %}
|
||||
<li>
|
||||
<a href="{{ menu_page.url }}">{{ menu_page.title }}</a>
|
||||
<li class="menu-item">
|
||||
<a href="{{ menu_page.url }}">
|
||||
<span class="main-menu-link">{{ menu_page.title }}</span>
|
||||
</a>
|
||||
{# Second-level: direct children of each top-level page #}
|
||||
{% with submenu=menu_page.get_children.live.in_menu %}
|
||||
{% if submenu %}
|
||||
<ul class="submenu">
|
||||
{% for subpage in submenu %}
|
||||
<li><a href="{{ subpage.url }}">{{ subpage.title }}</a></li>
|
||||
<li class="submenu-item">
|
||||
<a href="{{ subpage.url }}">
|
||||
<span class="submenu-item-link">{{ subpage.title }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
@ -51,20 +89,28 @@
|
||||
{# Optional extra links from settings #}
|
||||
{% if settings.base.HeaderSettings.main_links %}
|
||||
{% for item in settings.base.HeaderSettings.main_links %}
|
||||
<li><a href="{{ item.value.url }}">{{ item.value.label }}</a></li>
|
||||
<li class="menu-item">
|
||||
<a href="{{ item.value.url }}">
|
||||
<span class="main-menu-link">{{ item.value.label }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<form class="header-search" action="{% url 'search' %}" method="get" role="search">
|
||||
<div class="search-input">
|
||||
<button type="submit" class="search-icon" aria-label="搜尋">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M10 4a6 6 0 104.24 10.24l4.76 4.76 1.42-1.42-4.76-4.76A6 6 0 0010 4zm0 2a4 4 0 110 8 4 4 0 010-8z"/></svg>
|
||||
</button>
|
||||
<input
|
||||
type="search"
|
||||
name="query"
|
||||
placeholder="搜尋文章"
|
||||
value="{{ request.GET.query|default:'' }}"
|
||||
aria-label="搜尋文章">
|
||||
<button type="submit">搜尋</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</header>
|
||||
|
||||