- 默认零第三方自动请求:移除 21 个模板的 preconnect/dns-prefetch、PrismJS+autoloader、qrcode-generator 及内置一言/IP 定位 API - 一言与代码高亮配色改为自托管;欢迎弹窗自定义 IP 接口改为 opt-in;微信分享改为复制链接;LightGallery 按 GPLv3 使用 - README 新增「外部资源与隐私」「验证与回归测试」章节,修正无效链接并补充字体 OFL 来源;同步 docs 与修正虚构的目录树 - friends 等 12 处 #strings.substring 加空值保护;logo 由两份 1.5MB 压缩去重为单份 146KB - 新增 pnpm audit:external 与打包前置合规审计;制品补齐 34 个 docs 文档与预览图
688 lines
21 KiB
Plaintext
688 lines
21 KiB
Plaintext
---
|
||
import Layout from "../layouts/Layout.astro";
|
||
---
|
||
|
||
<Layout wide>
|
||
<Fragment slot="head">
|
||
<title th:text="|${theme.config?.links?.links_page_title ?: '友链'} - ${site.title}|"></title>
|
||
</Fragment>
|
||
|
||
<section
|
||
class="wi-links"
|
||
th:if="${pluginFinder.available('PluginLinks')}"
|
||
th:attr="data-style=${theme.config?.links?.links_style ?: 'cards'},data-random=${theme.config?.links?.links_random_order == true ? 'true' : 'false'}"
|
||
>
|
||
<div class="wi-container">
|
||
<div class="wi-links__header">
|
||
<h1
|
||
class="wi-links__title"
|
||
th:text="${theme.config?.links?.links_page_title ?: '友链'}"
|
||
>
|
||
友情链接
|
||
</h1>
|
||
<p class="wi-links__subtitle" th:text="'山水一程,三生有幸'">山水一程,三生有幸</p>
|
||
<span class="wi-page-accent"></span>
|
||
<button
|
||
type="button"
|
||
class="wi-links__portal"
|
||
id="wi-links-portal"
|
||
th:if="${theme.config?.links?.links_random_portal != false}"
|
||
title="随机传送"
|
||
aria-label="随机传送"
|
||
>
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
|
||
<polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline>
|
||
<polyline points="3.27 6.96 12 12.01 20.73 6.96" transform="translate(0 8)"></polyline>
|
||
</svg>
|
||
<span class="wi-links__portal-text">随机传送</span>
|
||
</button>
|
||
</div>
|
||
|
||
<th:block th:each="group : ${groups}">
|
||
<h2
|
||
class="wi-links__group"
|
||
th:if="${group.spec.displayName != null and !#strings.isEmpty(group.spec.displayName)}"
|
||
th:text="${group.spec.displayName}"
|
||
></h2>
|
||
<div class="wi-links__grid">
|
||
<a
|
||
th:each="link : ${group.links}"
|
||
th:href="${link.spec.url}"
|
||
th:title="${link.spec.displayName}"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
class="wi-links__card"
|
||
th:with="verification=${link.status?.verification},
|
||
accessState=${verification?.access?.state != null ? verification.access.state.name() : null}"
|
||
>
|
||
<div class="wi-links__card-glow"></div>
|
||
<div class="wi-links__card-inner">
|
||
<span class="wi-links__avatar-wrap">
|
||
<img
|
||
th:if="${link.spec.logo}"
|
||
th:src="${link.spec.logo}"
|
||
th:alt="${link.spec.displayName}"
|
||
class="wi-links__avatar"
|
||
/>
|
||
<div
|
||
th:unless="${link.spec.logo}"
|
||
class="wi-links__avatar wi-links__avatar--placeholder"
|
||
>
|
||
<span
|
||
th:text="${!#strings.isEmpty(link.spec.displayName) ? #strings.substring(link.spec.displayName, 0, 1) : '·'}"
|
||
></span>
|
||
</div>
|
||
<!-- 可达性检测徽章(plugin-links 后台「友链检测」产生):绿点=可访问,红点=异常,未检测不显示 -->
|
||
<span
|
||
class="wi-links__check wi-links__check--ok"
|
||
th:if="${accessState == 'ACCESSIBLE'}"
|
||
th:title="'站点访问正常'"
|
||
></span>
|
||
<span
|
||
class="wi-links__check wi-links__check--bad"
|
||
th:if="${accessState == 'INACCESSIBLE'}"
|
||
th:title="'站点访问异常'"
|
||
></span>
|
||
</span>
|
||
<div class="wi-links__info">
|
||
<span
|
||
class="wi-links__name"
|
||
th:text="${link.spec.displayName}"
|
||
></span>
|
||
<span
|
||
class="wi-links__desc"
|
||
th:if="${link.spec.description}"
|
||
th:text="${link.spec.description}"
|
||
></span>
|
||
</div>
|
||
<svg class="wi-links__arrow" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 17L17 7"/><path d="M7 7h10v10"/></svg>
|
||
</div>
|
||
</a>
|
||
</div>
|
||
</th:block>
|
||
|
||
<!--
|
||
友链申请:表单 POST 到插件的 /links/apply/submit,插件以 303 回跳 /links,
|
||
并带上 applied(success / error / disabled)、message、field、value 四个查询参数。
|
||
|
||
注意(踩过坑):th:attr 的赋值里不能调用 #lists 等表达式对象的方法,
|
||
Thymeleaf 会对 th:attr 走受限求值并抛
|
||
"Instantiation of new objects and access to static classes or parameters is forbidden"。
|
||
取值逻辑一律先在 th:with 里算成变量,再用 th:value 之类的单值属性输出。
|
||
-->
|
||
<div
|
||
class="wi-links__apply"
|
||
th:if="${theme.config?.links?.links_apply_form_enabled != false}"
|
||
th:with="appliedList=${param?.applied},
|
||
applied=${appliedList != null and appliedList.length > 0 ? appliedList[0] : null},
|
||
messageList=${param?.message},
|
||
applyMsg=${messageList != null and messageList.length > 0 ? messageList[0] : null},
|
||
fieldList=${param?.field},
|
||
applyField=${fieldList != null and fieldList.length > 0 ? fieldList[0] : null},
|
||
valueList=${param?.value},
|
||
applyValue=${valueList != null and valueList.length > 0 ? valueList[0] : null}"
|
||
>
|
||
<h2 class="wi-links__apply-title" th:text="'申请友链'">申请友链</h2>
|
||
<p
|
||
class="wi-links__apply-result"
|
||
th:if="${applied != null}"
|
||
th:classappend="${applied == 'success'} ? 'wi-links-check-ok' : 'wi-links-check-error'"
|
||
th:text="${applyMsg != null ? applyMsg : '申请已提交,等待站长审核 🌙'}"
|
||
role="status"
|
||
></p>
|
||
|
||
<th:block th:unless="${applied == 'disabled'}">
|
||
<form
|
||
class="wi-links__apply-form"
|
||
method="post"
|
||
th:action="@{/links/apply/submit}"
|
||
>
|
||
<input type="hidden" name="_csrf" th:value="${csrfToken}" />
|
||
<div class="wi-links__apply-row">
|
||
<label class="wi-links__apply-field">
|
||
<span th:text="'站点名称 *'">站点名称 *</span>
|
||
<input
|
||
type="text"
|
||
name="displayName"
|
||
required
|
||
maxlength="50"
|
||
placeholder="你的站点名称"
|
||
th:value="${applyField == 'displayName' ? applyValue : null}"
|
||
/>
|
||
</label>
|
||
<label class="wi-links__apply-field">
|
||
<span th:text="'站点链接 *'">站点链接 *</span>
|
||
<input
|
||
type="url"
|
||
name="url"
|
||
required
|
||
placeholder="https://"
|
||
th:value="${applyField == 'url' ? applyValue : null}"
|
||
/>
|
||
</label>
|
||
</div>
|
||
<div class="wi-links__apply-row">
|
||
<label class="wi-links__apply-field">
|
||
<span th:text="'图标地址'">图标地址</span>
|
||
<input type="url" name="logo" placeholder="https://" />
|
||
</label>
|
||
<label class="wi-links__apply-field">
|
||
<span th:text="'联系邮箱'">联系邮箱</span>
|
||
<input type="email" name="email" placeholder="用于通知审核结果" />
|
||
</label>
|
||
</div>
|
||
<label class="wi-links__apply-field">
|
||
<span th:text="'站点描述'">站点描述</span>
|
||
<input type="text" name="description" maxlength="120" placeholder="一句话介绍你的站点" />
|
||
</label>
|
||
<label class="wi-links__apply-field">
|
||
<span th:text="'反链页面'">反链页面</span>
|
||
<input type="url" name="backlink" placeholder="https://" />
|
||
</label>
|
||
<label class="wi-links__apply-field">
|
||
<span th:text="'RSS / Atom 地址(每行一个)'">RSS / Atom 地址(每行一个)</span>
|
||
<textarea name="feedUrls" rows="2" placeholder="https://"></textarea>
|
||
</label>
|
||
<div class="wi-links__apply-row">
|
||
<label class="wi-links__apply-field">
|
||
<span th:text="'验证码 *'">验证码 *</span>
|
||
<input type="text" name="captchaCode" required maxlength="8" autocomplete="off" placeholder="图中五位字符" />
|
||
</label>
|
||
<div class="wi-links__apply-captcha">
|
||
<img
|
||
class="wi-links__apply-captcha-img"
|
||
th:src="@{/links/apply/captcha}"
|
||
alt="验证码"
|
||
width="120"
|
||
height="44"
|
||
loading="lazy"
|
||
title="点击刷新验证码"
|
||
onclick="this.src=this.src.split('?')[0]+'?ts='+Date.now()"
|
||
onerror="var c=this.closest('.wi-links__apply');if(c)c.classList.add('wi-links__apply--unavailable')"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<button
|
||
class="wi-links__apply-submit"
|
||
type="submit"
|
||
th:text="'提交申请'"
|
||
>
|
||
提交申请
|
||
</button>
|
||
</form>
|
||
</th:block>
|
||
<p
|
||
class="wi-links__apply-unavailable-tip"
|
||
th:text="'友链申请暂未开放。'"
|
||
hidden
|
||
></p>
|
||
</div>
|
||
|
||
<div class="wi-links__comment" th:if="${theme.config?.links?.links_show_comment == true}">
|
||
<halo:comment group="plugin.halo.run" kind="Plugin" name="PluginLinks"></halo:comment>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<script is:inline th:inline="none">
|
||
(function () {
|
||
var section = document.querySelector(".wi-links");
|
||
if (!section) return;
|
||
|
||
var cards = Array.prototype.slice.call(section.querySelectorAll(".wi-links__card"));
|
||
if (!cards.length) return;
|
||
|
||
// 随机排列:Fisher-Yates 打乱每个分组内的卡片顺序
|
||
if (section.getAttribute("data-random") === "true") {
|
||
Array.prototype.forEach.call(section.querySelectorAll(".wi-links__grid"), function (grid) {
|
||
for (var i = grid.children.length; i >= 0; i--) {
|
||
var j = Math.floor(Math.random() * i);
|
||
grid.appendChild(grid.children[j]);
|
||
}
|
||
});
|
||
}
|
||
|
||
// 随机传送:从当前页所有友链中随机打开一个
|
||
var portal = document.getElementById("wi-links-portal");
|
||
if (portal) {
|
||
portal.addEventListener("click", function () {
|
||
var links = Array.prototype.slice
|
||
.call(section.querySelectorAll(".wi-links__card"))
|
||
.map(function (a) {
|
||
return a.getAttribute("href");
|
||
})
|
||
.filter(function (h) {
|
||
return h && h !== "#";
|
||
});
|
||
if (!links.length) return;
|
||
var target = links[Math.floor(Math.random() * links.length)];
|
||
window.open(target, "_blank", "noopener");
|
||
});
|
||
}
|
||
})();
|
||
</script>
|
||
</Layout>
|
||
|
||
<style>
|
||
.wi-links {
|
||
padding-block: 1rem;
|
||
}
|
||
|
||
.wi-links__header {
|
||
text-align: center;
|
||
margin-bottom: var(--space-3xl);
|
||
}
|
||
|
||
.wi-links__portal {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.375rem;
|
||
padding: 0.6rem 1.1rem;
|
||
min-height: 44px;
|
||
margin-top: var(--space-md);
|
||
border-radius: 9999px;
|
||
font-size: var(--text-sm);
|
||
color: #fff;
|
||
background: linear-gradient(
|
||
135deg,
|
||
var(--accent),
|
||
color-mix(in srgb, var(--accent) 70%, #000)
|
||
);
|
||
border: none;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
font-weight: 600;
|
||
box-shadow: 0 2px 8px color-mix(in srgb, var(--accent) 30%, transparent);
|
||
transition: transform var(--duration-fast) var(--ease-out-quart),
|
||
box-shadow var(--duration-fast) var(--ease-out-quart),
|
||
opacity var(--duration-fast) var(--ease-out-quart);
|
||
}
|
||
|
||
.wi-links__portal:hover {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 14px color-mix(in srgb, var(--accent) 40%, transparent);
|
||
opacity: 0.95;
|
||
}
|
||
|
||
.wi-links__portal:active {
|
||
transform: translateY(0) scale(0.97);
|
||
}
|
||
|
||
.wi-links__portal svg {
|
||
transition: transform var(--duration-normal) var(--ease-out-expo);
|
||
}
|
||
|
||
.wi-links__portal:hover svg {
|
||
transform: rotate(180deg) scale(1.1);
|
||
}
|
||
|
||
.wi-links__comment {
|
||
margin-top: var(--space-3xl);
|
||
}
|
||
|
||
/* ===== 友链申请表单 ===== */
|
||
.wi-links__apply {
|
||
margin-top: var(--space-3xl);
|
||
padding: var(--space-xl);
|
||
border: 1px solid var(--rule);
|
||
border-radius: var(--radius-lg, 16px);
|
||
background: var(--bg-raised);
|
||
}
|
||
|
||
.wi-links__apply-title {
|
||
text-align: center;
|
||
font-size: var(--text-xl);
|
||
font-weight: 700;
|
||
color: var(--ink);
|
||
margin-bottom: var(--space-lg);
|
||
}
|
||
|
||
.wi-links__apply-result {
|
||
text-align: center;
|
||
font-size: var(--text-sm);
|
||
color: var(--accent);
|
||
margin-bottom: var(--space-md);
|
||
}
|
||
|
||
.wi-links__apply-result--ok,
|
||
.wi-links-check-ok {
|
||
color: var(--accent);
|
||
}
|
||
|
||
.wi-links__apply-result--error,
|
||
.wi-links-check-error {
|
||
color: #e6162d;
|
||
}
|
||
|
||
html.dark .wi-links__apply-result--error,
|
||
html.dark .wi-links-check-error {
|
||
color: #ff6b6b;
|
||
}
|
||
|
||
.wi-links__apply-form {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--space-md);
|
||
}
|
||
|
||
.wi-links__apply-row {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: var(--space-md);
|
||
}
|
||
|
||
.wi-links__apply-field {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.35rem;
|
||
font-size: var(--text-sm);
|
||
color: var(--ink-2);
|
||
}
|
||
|
||
.wi-links__apply-field input,
|
||
.wi-links__apply-field textarea {
|
||
padding: 0.6rem 0.8rem;
|
||
border: 1px solid var(--rule);
|
||
border-radius: 10px;
|
||
background: var(--bg);
|
||
color: var(--ink);
|
||
font-family: inherit;
|
||
font-size: var(--text-sm);
|
||
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
||
resize: vertical;
|
||
}
|
||
|
||
.wi-links__apply-field input:focus,
|
||
.wi-links__apply-field textarea:focus {
|
||
outline: none;
|
||
border-color: var(--accent);
|
||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 15%, transparent);
|
||
}
|
||
|
||
.wi-links__apply-captcha {
|
||
display: flex;
|
||
align-items: flex-end;
|
||
padding-bottom: 2px;
|
||
}
|
||
|
||
.wi-links__apply-captcha-img {
|
||
border-radius: 8px;
|
||
border: 1px solid var(--rule);
|
||
cursor: pointer;
|
||
}
|
||
|
||
.wi-links__apply-submit {
|
||
align-self: center;
|
||
padding: 0.65rem 2.2rem;
|
||
min-height: 44px;
|
||
border: none;
|
||
border-radius: 9999px;
|
||
font-size: var(--text-sm);
|
||
font-weight: 600;
|
||
font-family: inherit;
|
||
color: #fff;
|
||
background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent) 70%, #000));
|
||
cursor: pointer;
|
||
transition: transform var(--duration-fast) var(--ease-out-quart), opacity 0.15s ease;
|
||
}
|
||
|
||
.wi-links__apply-submit:hover {
|
||
transform: translateY(-1px);
|
||
opacity: 0.95;
|
||
}
|
||
|
||
.wi-links__apply-submit:active {
|
||
transform: translateY(0) scale(0.98);
|
||
}
|
||
|
||
/* 插件未开放申请时(验证码 404)自动隐藏表单并提示 */
|
||
.wi-links__apply--unavailable .wi-links__apply-form {
|
||
display: none;
|
||
}
|
||
|
||
.wi-links__apply--unavailable .wi-links__apply-unavailable-tip {
|
||
display: block !important;
|
||
text-align: center;
|
||
color: var(--ink-3);
|
||
font-size: var(--text-sm);
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.wi-links__apply-row {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
|
||
.wi-links__title {
|
||
font-size: var(--text-4xl);
|
||
margin-bottom: var(--space-xs);
|
||
}
|
||
|
||
.wi-links__subtitle {
|
||
font-size: var(--text-sm);
|
||
color: var(--ink-3);
|
||
letter-spacing: var(--tracking-wide);
|
||
margin-block-end: 0;
|
||
}
|
||
|
||
.wi-page-accent {
|
||
display: block;
|
||
width: 48px;
|
||
height: 3px;
|
||
margin: var(--space-md) auto 0;
|
||
border-radius: 9999px;
|
||
background: var(--accent);
|
||
}
|
||
|
||
.wi-links__group {
|
||
font-size: var(--text-lg);
|
||
color: var(--ink-2);
|
||
margin-bottom: var(--space-lg);
|
||
padding-bottom: var(--space-sm);
|
||
border-bottom: 1px solid var(--rule);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.wi-links__grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||
gap: var(--space-md);
|
||
margin-bottom: var(--space-2xl);
|
||
}
|
||
|
||
.wi-links[data-style="list"] .wi-links__grid {
|
||
grid-template-columns: 1fr;
|
||
max-width: 720px;
|
||
}
|
||
|
||
.wi-links[data-style="list"] .wi-links__card-inner {
|
||
border-radius: 12px;
|
||
padding: var(--space-md) var(--space-lg);
|
||
}
|
||
|
||
.wi-links[data-style="grid"] .wi-links__grid {
|
||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||
gap: var(--space-sm);
|
||
}
|
||
|
||
.wi-links[data-style="grid"] .wi-links__card-inner {
|
||
flex-direction: column;
|
||
align-items: center;
|
||
text-align: center;
|
||
padding: var(--space-xl) var(--space-lg);
|
||
}
|
||
|
||
.wi-links[data-style="grid"] .wi-links__arrow {
|
||
display: none;
|
||
}
|
||
|
||
.wi-links__card {
|
||
position: relative;
|
||
border-radius: 16px;
|
||
text-decoration: none;
|
||
color: inherit;
|
||
display: flex;
|
||
/* 注意:这里绝不能加 overflow:hidden —— 悬浮时内层卡片会上移 4px 并投射大范围
|
||
阴影,裁切会把阴影切出一条"断开的光带"、卡片顶部被削平(v1.2.0 修复) */
|
||
}
|
||
|
||
.wi-links__card-glow {
|
||
position: absolute;
|
||
inset: 0;
|
||
border-radius: 16px;
|
||
opacity: 0;
|
||
background: linear-gradient(
|
||
135deg,
|
||
color-mix(in srgb, var(--accent) 8%, transparent) 0%,
|
||
transparent 60%
|
||
);
|
||
transition: opacity var(--duration-normal) var(--ease-out-expo);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.wi-links__card:hover .wi-links__card-glow {
|
||
opacity: 1;
|
||
}
|
||
|
||
.wi-links__card-inner {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-md);
|
||
padding: var(--space-lg);
|
||
background: var(--glass-bg);
|
||
border: 1px solid var(--glass-border);
|
||
border-radius: 16px;
|
||
flex: 1;
|
||
transition:
|
||
transform var(--duration-normal) var(--ease-out-expo),
|
||
box-shadow var(--duration-normal) var(--ease-out-expo),
|
||
border-color var(--duration-normal) var(--ease-out-expo);
|
||
}
|
||
|
||
.wi-links__card:hover .wi-links__card-inner {
|
||
transform: translateY(-4px);
|
||
box-shadow: var(--shadow-lg);
|
||
border-color: color-mix(in srgb, var(--accent) 40%, var(--glass-border));
|
||
}
|
||
|
||
.wi-links__avatar-wrap {
|
||
position: relative;
|
||
flex: none;
|
||
display: inline-flex;
|
||
line-height: 1;
|
||
}
|
||
|
||
.wi-links__avatar {
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 12px;
|
||
object-fit: cover;
|
||
flex-shrink: 0;
|
||
box-shadow: var(--shadow-sm);
|
||
transition: transform var(--duration-normal) var(--ease-out-expo);
|
||
}
|
||
|
||
/* 反链 / 可达性检测徽章:绿色正常,红色异常 */
|
||
.wi-links__check {
|
||
position: absolute;
|
||
right: -3px;
|
||
bottom: -3px;
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
border: 2px solid var(--bg);
|
||
box-shadow: var(--shadow-sm);
|
||
}
|
||
|
||
.wi-links__check--ok {
|
||
background: #22c55e;
|
||
}
|
||
|
||
.wi-links__check--bad {
|
||
background: #ef4444;
|
||
}
|
||
|
||
.wi-links__card:hover .wi-links__avatar {
|
||
transform: scale(1.08);
|
||
}
|
||
|
||
.wi-links__avatar--placeholder {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--accent-bg);
|
||
color: var(--accent);
|
||
font-family: var(--font-sans);
|
||
font-size: var(--text-lg);
|
||
font-weight: 700;
|
||
box-shadow: none;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.wi-links__info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
min-width: 0;
|
||
flex: 1;
|
||
}
|
||
|
||
.wi-links__name {
|
||
font-size: var(--text-md);
|
||
font-weight: 600;
|
||
color: var(--ink);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
transition: color var(--duration-fast) var(--ease-out-quart);
|
||
}
|
||
|
||
.wi-links__card:hover .wi-links__name {
|
||
color: var(--accent);
|
||
}
|
||
|
||
.wi-links__desc {
|
||
font-size: var(--text-sm);
|
||
color: var(--ink-3);
|
||
line-height: var(--leading-snug);
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.wi-links__arrow {
|
||
flex-shrink: 0;
|
||
color: var(--ink-3);
|
||
opacity: 0;
|
||
transform: translate(-4px, 4px);
|
||
transition:
|
||
opacity var(--duration-normal) var(--ease-out-expo),
|
||
transform var(--duration-normal) var(--ease-out-expo),
|
||
color var(--duration-fast) var(--ease-out-quart);
|
||
}
|
||
|
||
.wi-links__card:hover .wi-links__arrow {
|
||
opacity: 1;
|
||
transform: translate(0, 0);
|
||
color: var(--accent);
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.wi-links__grid {
|
||
grid-template-columns: 1fr;
|
||
gap: var(--space-sm);
|
||
}
|
||
|
||
.wi-links__card-inner {
|
||
padding: var(--space-md);
|
||
}
|
||
|
||
.wi-links__avatar {
|
||
width: 42px;
|
||
height: 42px;
|
||
}
|
||
}
|
||
</style>
|