update at 2026-03-16 09:00:35
This commit is contained in:
@@ -18,6 +18,7 @@ trap 'kill "$SERVER_PID" 2>/dev/null || true' EXIT INT TERM
|
||||
|
||||
sleep 1
|
||||
/usr/bin/swift "$CALENDAR_DIR/scripts/export-kindle-background.swift" "$URL" "$OUT_PNG" "$OUT_REGION"
|
||||
|
||||
node "$CALENDAR_DIR/scripts/generate-dashboard-manifest.mjs" >/dev/null
|
||||
|
||||
cat "$OUT_REGION"
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
import AppKit
|
||||
import Foundation
|
||||
import ImageIO
|
||||
import UniformTypeIdentifiers
|
||||
import WebKit
|
||||
|
||||
enum ExportError: Error, CustomStringConvertible {
|
||||
@@ -29,7 +31,8 @@ final class SnapshotExporter: NSObject, WKNavigationDelegate {
|
||||
private let pngOutputURL: URL
|
||||
private let regionOutputURL: URL
|
||||
private let completion: (Result<Void, Error>) -> Void
|
||||
private let targetSize = CGSize(width: 1024, height: 600)
|
||||
// 直接按 Kindle Voyage 的系统屏保尺寸导出,避免额外旋转和补边。
|
||||
private let targetSize = CGSize(width: 1072, height: 1448)
|
||||
|
||||
private lazy var window: NSWindow = {
|
||||
let window = NSWindow(
|
||||
@@ -129,16 +132,52 @@ final class SnapshotExporter: NSObject, WKNavigationDelegate {
|
||||
image.draw(in: NSRect(origin: .zero, size: targetSize))
|
||||
normalizedImage.unlockFocus()
|
||||
|
||||
guard
|
||||
let tiffRepresentation = normalizedImage.tiffRepresentation,
|
||||
let bitmap = NSBitmapImageRep(data: tiffRepresentation),
|
||||
let pngData = bitmap.representation(using: .png, properties: [:])
|
||||
else {
|
||||
guard let sourceCGImage = normalizedImage.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
|
||||
throw ExportError.pngEncodingFailed(url.path)
|
||||
}
|
||||
|
||||
let width = Int(targetSize.width)
|
||||
let height = Int(targetSize.height)
|
||||
let colorSpace = CGColorSpaceCreateDeviceGray()
|
||||
|
||||
// 输出 8-bit 灰度 PNG,但页面本身仍按纯白底和纯黑字设计,避免额外灰阶装饰。
|
||||
guard let context = CGContext(
|
||||
data: nil,
|
||||
width: width,
|
||||
height: height,
|
||||
bitsPerComponent: 8,
|
||||
bytesPerRow: 0,
|
||||
space: colorSpace,
|
||||
bitmapInfo: CGImageAlphaInfo.none.rawValue
|
||||
) else {
|
||||
throw ExportError.pngEncodingFailed(url.path)
|
||||
}
|
||||
|
||||
context.setFillColor(gray: 1, alpha: 1)
|
||||
context.fill(CGRect(x: 0, y: 0, width: width, height: height))
|
||||
context.interpolationQuality = .high
|
||||
context.draw(sourceCGImage, in: CGRect(x: 0, y: 0, width: width, height: height))
|
||||
|
||||
guard let grayscaleImage = context.makeImage() else {
|
||||
throw ExportError.pngEncodingFailed(url.path)
|
||||
}
|
||||
|
||||
try FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true)
|
||||
try pngData.write(to: url)
|
||||
|
||||
guard let destination = CGImageDestinationCreateWithURL(
|
||||
url as CFURL,
|
||||
UTType.png.identifier as CFString,
|
||||
1,
|
||||
nil
|
||||
) else {
|
||||
throw ExportError.pngEncodingFailed(url.path)
|
||||
}
|
||||
|
||||
CGImageDestinationAddImage(destination, grayscaleImage, nil)
|
||||
|
||||
guard CGImageDestinationFinalize(destination) else {
|
||||
throw ExportError.pngEncodingFailed(url.path)
|
||||
}
|
||||
}
|
||||
|
||||
private func saveRegion(region: [String: NSNumber]) throws {
|
||||
|
||||
@@ -58,8 +58,7 @@ const stageStyle = computed(() => ({
|
||||
:style="stageStyle"
|
||||
data-clock-region="true"
|
||||
>
|
||||
<div v-if="mode === 'background'" class="analog-clock__placeholder" />
|
||||
<template v-else>
|
||||
<template v-if="mode !== 'background'">
|
||||
<img class="analog-clock__face" :src="CLOCK_FACE_ASSET" alt="时钟表盘" />
|
||||
<img
|
||||
v-if="mode === 'full'"
|
||||
@@ -88,8 +87,7 @@ const stageStyle = computed(() => ({
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.analog-clock__face,
|
||||
.analog-clock__placeholder {
|
||||
.analog-clock__face {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -98,15 +96,6 @@ const stageStyle = computed(() => ({
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.analog-clock__placeholder {
|
||||
border-radius: 50%;
|
||||
background:
|
||||
radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.94), rgba(255, 250, 246, 0.78) 68%, rgba(239, 226, 221, 0.52));
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(214, 196, 187, 0.35),
|
||||
inset 0 0 24px rgba(255, 255, 255, 0.45);
|
||||
}
|
||||
|
||||
.analog-clock__hand {
|
||||
position: absolute;
|
||||
max-width: none;
|
||||
|
||||
@@ -97,30 +97,30 @@ function subLabelTone(cell: CalendarModel['cells'][number]) {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
height: 100%;
|
||||
gap: 0.95rem;
|
||||
padding: 1.28rem 1.32rem 1.12rem;
|
||||
gap: 1rem;
|
||||
padding: 1.28rem 1.28rem 1.16rem;
|
||||
}
|
||||
|
||||
.calendar-card__hero {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 0.9rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.calendar-card__headline {
|
||||
display: grid;
|
||||
align-content: center;
|
||||
align-content: start;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
gap: 0.18rem;
|
||||
gap: 0.24rem;
|
||||
}
|
||||
|
||||
.calendar-card__headline-copy {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.45rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.calendar-card__day {
|
||||
@@ -128,33 +128,34 @@ function subLabelTone(cell: CalendarModel['cells'][number]) {
|
||||
'Iowan Old Style',
|
||||
'Baskerville',
|
||||
serif;
|
||||
font-size: 5.7rem;
|
||||
font-size: 6.9rem;
|
||||
line-height: 0.88;
|
||||
letter-spacing: -0.08em;
|
||||
color: #111111;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.calendar-card__lunar-day,
|
||||
.calendar-card__weekday {
|
||||
margin: 0;
|
||||
font-size: 1.48rem;
|
||||
font-size: 1.88rem;
|
||||
line-height: 1.02;
|
||||
color: #232323;
|
||||
color: #000000;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.calendar-card__weekday {
|
||||
font-size: 1.54rem;
|
||||
font-size: 1.88rem;
|
||||
}
|
||||
|
||||
.calendar-card__panel {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
gap: 0.45rem;
|
||||
gap: 0.55rem;
|
||||
min-height: 0;
|
||||
padding: 0.74rem 0.9rem 0.84rem;
|
||||
border-radius: 1.1rem;
|
||||
background: linear-gradient(180deg, rgba(250, 237, 238, 0.58), rgba(247, 240, 239, 0.86));
|
||||
padding: 0.88rem 0.94rem 0.94rem;
|
||||
border-radius: 1.25rem;
|
||||
border: 2px solid var(--frame-stroke);
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.calendar-card__panel-header {
|
||||
@@ -176,8 +177,8 @@ function subLabelTone(cell: CalendarModel['cells'][number]) {
|
||||
|
||||
.calendar-card__panel-subtitle {
|
||||
margin-top: 0.2rem;
|
||||
font-size: 0.76rem;
|
||||
color: #7a6a61;
|
||||
font-size: 0.84rem;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.calendar-card__badges {
|
||||
@@ -188,36 +189,21 @@ function subLabelTone(cell: CalendarModel['cells'][number]) {
|
||||
}
|
||||
|
||||
.calendar-card__badge {
|
||||
padding: 0.12rem 0.42rem;
|
||||
padding: 0.14rem 0.46rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.66rem;
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.2;
|
||||
border: 1px solid currentColor;
|
||||
background: rgba(255, 255, 255, 0.66);
|
||||
}
|
||||
|
||||
.calendar-card__badge--holiday {
|
||||
color: #8b4a20;
|
||||
}
|
||||
|
||||
.calendar-card__badge--workday {
|
||||
color: #7c5d2f;
|
||||
}
|
||||
|
||||
.calendar-card__badge--festival {
|
||||
color: #67503d;
|
||||
}
|
||||
|
||||
.calendar-card__badge--term {
|
||||
color: #4a6b6f;
|
||||
color: #000000;
|
||||
border: 1.5px solid var(--frame-stroke);
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.calendar-card__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||
grid-auto-rows: minmax(0, 1fr);
|
||||
column-gap: 0.16rem;
|
||||
row-gap: 0.04rem;
|
||||
column-gap: 0.18rem;
|
||||
row-gap: 0.1rem;
|
||||
min-height: 0;
|
||||
align-content: start;
|
||||
}
|
||||
@@ -225,18 +211,19 @@ function subLabelTone(cell: CalendarModel['cells'][number]) {
|
||||
.calendar-card__week-label {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding-bottom: 0.08rem;
|
||||
font-size: 0.7rem;
|
||||
color: #7b7b7b;
|
||||
padding-bottom: 0.14rem;
|
||||
font-size: 0.82rem;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.calendar-card__cell {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
min-height: 0;
|
||||
padding: 0.08rem 0 0.1rem;
|
||||
border-radius: 0.78rem;
|
||||
color: #3f454e;
|
||||
padding: 0.16rem 0 0.18rem;
|
||||
border-radius: 0.9rem;
|
||||
border: 1.5px solid transparent;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.calendar-card__cell-copy {
|
||||
@@ -246,59 +233,26 @@ function subLabelTone(cell: CalendarModel['cells'][number]) {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.calendar-card__cell--muted {
|
||||
color: #b7b0b3;
|
||||
}
|
||||
|
||||
.calendar-card__cell--today {
|
||||
background: #171717;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.calendar-card__cell--holiday .calendar-card__solar {
|
||||
color: #b76114;
|
||||
border-color: var(--frame-stroke-strong);
|
||||
}
|
||||
|
||||
.calendar-card__solar {
|
||||
font-size: 0.92rem;
|
||||
font-size: 0.98rem;
|
||||
line-height: 1.05;
|
||||
}
|
||||
|
||||
.calendar-card__sub {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
font-size: 0.42rem;
|
||||
font-size: 0.84rem;
|
||||
line-height: 1;
|
||||
color: #8b8b8b;
|
||||
color: #000000;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.calendar-card__sub--holiday {
|
||||
color: #b76114;
|
||||
}
|
||||
|
||||
.calendar-card__sub--workday {
|
||||
color: #7c5d2f;
|
||||
}
|
||||
|
||||
.calendar-card__sub--festival {
|
||||
color: #67503d;
|
||||
}
|
||||
|
||||
.calendar-card__sub--term {
|
||||
color: #4a6b6f;
|
||||
}
|
||||
|
||||
.calendar-card__sub--lunar {
|
||||
color: #8b8b8b;
|
||||
}
|
||||
|
||||
.calendar-card__cell--muted .calendar-card__sub {
|
||||
color: #c3bbbe;
|
||||
}
|
||||
|
||||
.calendar-card__cell--today .calendar-card__sub {
|
||||
color: rgba(255, 255, 255, 0.84);
|
||||
.calendar-card__cell--muted {
|
||||
border-color: rgba(139, 107, 71, 0.35);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -42,7 +42,7 @@ const quoteFontSize = computed(() => {
|
||||
display: grid;
|
||||
gap: 0.34rem;
|
||||
padding: 0.72rem 1.02rem;
|
||||
background: linear-gradient(180deg, rgba(255, 248, 230, 0.96), rgba(255, 252, 243, 0.94));
|
||||
background: #ffffff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -50,12 +50,13 @@ const quoteFontSize = computed(() => {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
color: #c75d00;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.quote-card__icon {
|
||||
width: 0.9rem;
|
||||
height: 0.9rem;
|
||||
filter: brightness(0) saturate(100%);
|
||||
}
|
||||
|
||||
.quote-card__title {
|
||||
@@ -66,6 +67,6 @@ const quoteFontSize = computed(() => {
|
||||
.quote-card__content {
|
||||
margin: 0;
|
||||
line-height: 1.34;
|
||||
color: #292929;
|
||||
color: #000000;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
import { computed } from 'vue';
|
||||
|
||||
import WeatherGlyph from './WeatherGlyph.vue';
|
||||
import {
|
||||
HUMIDITY_ICON_ASSET,
|
||||
SUNRISE_ICON_ASSET,
|
||||
SUNSET_ICON_ASSET,
|
||||
VISIBILITY_ICON_ASSET,
|
||||
WIND_SPEED_ICON_ASSET,
|
||||
} from '@/lib/icon-assets';
|
||||
import type { ForecastDay, WeatherSnapshot } from '@/lib/weather';
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -54,11 +61,13 @@ const metrics = computed(() => {
|
||||
label: '日出',
|
||||
value: props.weather.sunrise,
|
||||
accent: 'metric-pill--sunrise',
|
||||
icon: SUNRISE_ICON_ASSET,
|
||||
},
|
||||
{
|
||||
label: '日落',
|
||||
value: props.weather.sunset,
|
||||
accent: 'metric-pill--sunset',
|
||||
icon: SUNSET_ICON_ASSET,
|
||||
},
|
||||
{
|
||||
label: '空气质量',
|
||||
@@ -67,11 +76,13 @@ const metrics = computed(() => {
|
||||
? '暂无'
|
||||
: `${props.weather.aqi}${props.weather.aqiLabel}`,
|
||||
accent: 'metric-pill--air',
|
||||
icon: null,
|
||||
},
|
||||
{
|
||||
label: '能见度',
|
||||
value: `${props.weather.visibilityKm} km`,
|
||||
accent: 'metric-pill--visibility',
|
||||
icon: VISIBILITY_ICON_ASSET,
|
||||
},
|
||||
] as const;
|
||||
});
|
||||
@@ -115,11 +126,11 @@ function forecastKind(day: ForecastDay) {
|
||||
|
||||
<div class="weather-card__facts">
|
||||
<div class="weather-card__fact">
|
||||
<span class="weather-card__fact-dot weather-card__fact-dot--humidity" />
|
||||
<img class="weather-card__fact-icon" :src="HUMIDITY_ICON_ASSET" alt="" aria-hidden="true" />
|
||||
<span>湿度 {{ weather.humidity }}%</span>
|
||||
</div>
|
||||
<div class="weather-card__fact">
|
||||
<span class="weather-card__fact-dot weather-card__fact-dot--wind" />
|
||||
<img class="weather-card__fact-icon" :src="WIND_SPEED_ICON_ASSET" alt="" aria-hidden="true" />
|
||||
<span>风速 {{ weather.windSpeed }} km/h</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -144,7 +155,14 @@ function forecastKind(day: ForecastDay) {
|
||||
:class="['metric-pill', metric.accent]"
|
||||
>
|
||||
<div class="metric-pill__label">
|
||||
<span class="metric-pill__dot" />
|
||||
<img
|
||||
v-if="metric.icon"
|
||||
class="metric-pill__icon"
|
||||
:src="metric.icon"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span v-else class="metric-pill__dot" />
|
||||
<span>{{ metric.label }}</span>
|
||||
</div>
|
||||
<p class="metric-pill__value">{{ metric.value }}</p>
|
||||
@@ -159,8 +177,8 @@ function forecastKind(day: ForecastDay) {
|
||||
grid-template-rows: auto minmax(0, 1.15fr) minmax(0, 0.9fr) minmax(0, 1fr);
|
||||
align-content: stretch;
|
||||
height: 100%;
|
||||
gap: 0.65rem;
|
||||
padding: 1.05rem 1.1rem 0.92rem;
|
||||
gap: 0.72rem;
|
||||
padding: 1.08rem 1.12rem 0.98rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -176,16 +194,16 @@ function forecastKind(day: ForecastDay) {
|
||||
}
|
||||
|
||||
.weather-card__title {
|
||||
font-size: 2.2rem;
|
||||
font-size: 2.16rem;
|
||||
font-weight: 700;
|
||||
color: #111111;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.weather-card__subtitle {
|
||||
margin-top: 0.14rem;
|
||||
font-size: 1.18rem;
|
||||
font-size: 1.12rem;
|
||||
line-height: 1.08;
|
||||
color: #707070;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.weather-card__hero {
|
||||
@@ -196,13 +214,14 @@ function forecastKind(day: ForecastDay) {
|
||||
min-height: 0;
|
||||
padding: 0.88rem 0.94rem;
|
||||
border-radius: 1rem;
|
||||
background: linear-gradient(180deg, #dfeaf8, #d7e4f6);
|
||||
border: 2px solid var(--frame-stroke);
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.weather-card__hero--placeholder {
|
||||
justify-content: center;
|
||||
min-height: 5.75rem;
|
||||
color: #617288;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.weather-card__hero-main {
|
||||
@@ -215,14 +234,14 @@ function forecastKind(day: ForecastDay) {
|
||||
.weather-card__temperature {
|
||||
font-size: 2.8rem;
|
||||
line-height: 0.94;
|
||||
color: #111111;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.weather-card__condition {
|
||||
margin-top: 0.18rem;
|
||||
font-size: 1.36rem;
|
||||
line-height: 1.05;
|
||||
color: #2c3641;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.weather-card__facts {
|
||||
@@ -237,13 +256,21 @@ function forecastKind(day: ForecastDay) {
|
||||
display: inline-flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.45rem;
|
||||
color: #617288;
|
||||
color: #000000;
|
||||
font-size: 1.08rem;
|
||||
line-height: 1.06;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.weather-card__fact-dot,
|
||||
.weather-card__fact-icon,
|
||||
.metric-pill__icon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
object-fit: contain;
|
||||
flex: 0 0 auto;
|
||||
filter: brightness(0) saturate(100%);
|
||||
}
|
||||
|
||||
.metric-pill__dot {
|
||||
width: 0.42rem;
|
||||
height: 0.42rem;
|
||||
@@ -252,14 +279,6 @@ function forecastKind(day: ForecastDay) {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.weather-card__fact-dot--humidity {
|
||||
color: #5c84be;
|
||||
}
|
||||
|
||||
.weather-card__fact-dot--wind {
|
||||
color: #6c7e95;
|
||||
}
|
||||
|
||||
.weather-card__forecast {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
@@ -275,7 +294,8 @@ function forecastKind(day: ForecastDay) {
|
||||
min-height: 0;
|
||||
padding: 0.68rem 0.28rem;
|
||||
border-radius: 1rem;
|
||||
background: #f8f7f6;
|
||||
border: 2px solid var(--frame-stroke);
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.forecast-pill__label,
|
||||
@@ -286,19 +306,19 @@ function forecastKind(day: ForecastDay) {
|
||||
.forecast-pill__label {
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
color: #5a5a5a;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.forecast-pill__temp {
|
||||
font-size: 1.24rem;
|
||||
line-height: 1;
|
||||
color: #1e1e1e;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.forecast-pill__temp span {
|
||||
margin-left: 0.12rem;
|
||||
font-size: 1rem;
|
||||
color: #7a7a7a;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.weather-card__metrics {
|
||||
@@ -316,6 +336,9 @@ function forecastKind(day: ForecastDay) {
|
||||
min-height: 0;
|
||||
padding: 0.68rem 0.74rem;
|
||||
border-radius: 0.95rem;
|
||||
border: 2px solid var(--frame-stroke);
|
||||
background: #ffffff;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.metric-pill__label,
|
||||
@@ -334,31 +357,12 @@ function forecastKind(day: ForecastDay) {
|
||||
.metric-pill__value {
|
||||
font-size: 1.34rem;
|
||||
line-height: 1.02;
|
||||
color: #1c1c1c;
|
||||
}
|
||||
|
||||
.metric-pill--sunrise {
|
||||
background: #fff1dd;
|
||||
color: #cb6800;
|
||||
}
|
||||
|
||||
.metric-pill--sunset {
|
||||
background: #efe4ff;
|
||||
color: #8e42e8;
|
||||
}
|
||||
|
||||
.metric-pill--air {
|
||||
background: #dbf5df;
|
||||
color: #1f9451;
|
||||
}
|
||||
|
||||
.metric-pill--visibility {
|
||||
background: #d8f4fa;
|
||||
color: #17779d;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.weather-card :deep(.glyph--large) {
|
||||
width: 2.8rem;
|
||||
height: 2.8rem;
|
||||
filter: brightness(0) saturate(100%);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -26,6 +26,7 @@ const source = computed(() => weatherIconForKind(props.kind));
|
||||
height: 1.25rem;
|
||||
flex-shrink: 0;
|
||||
object-fit: contain;
|
||||
filter: brightness(0) saturate(100%);
|
||||
}
|
||||
|
||||
.glyph--large {
|
||||
|
||||
@@ -2,10 +2,15 @@ import bookIcon from '../../../assets/书摘.svg';
|
||||
import cloudyIcon from '../../../assets/多云.svg';
|
||||
import heavyRainIcon from '../../../assets/大雨.svg';
|
||||
import snowIcon from '../../../assets/大雪.svg';
|
||||
import sunriseIcon from '../../../assets/日出.svg';
|
||||
import sunsetIcon from '../../../assets/日落.svg';
|
||||
import lightRainIcon from '../../../assets/小雨.svg';
|
||||
import nightIcon from '../../../assets/晚上.svg';
|
||||
import clearIcon from '../../../assets/晴天.svg';
|
||||
import humidityIcon from '../../../assets/湿度.svg';
|
||||
import visibilityIcon from '../../../assets/能见度.svg';
|
||||
import sleetIcon from '../../../assets/雨夹雪.svg';
|
||||
import windSpeedIcon from '../../../assets/风速.svg';
|
||||
|
||||
export type WeatherIconKind =
|
||||
| 'clear'
|
||||
@@ -37,3 +42,8 @@ export function weatherIconForKind(kind: WeatherIconKind) {
|
||||
}
|
||||
|
||||
export const QUOTE_ICON_ASSET = bookIcon;
|
||||
export const HUMIDITY_ICON_ASSET = humidityIcon;
|
||||
export const WIND_SPEED_ICON_ASSET = windSpeedIcon;
|
||||
export const SUNRISE_ICON_ASSET = sunriseIcon;
|
||||
export const SUNSET_ICON_ASSET = sunsetIcon;
|
||||
export const VISIBILITY_ICON_ASSET = visibilityIcon;
|
||||
|
||||
@@ -4,11 +4,14 @@
|
||||
'PingFang SC',
|
||||
'Noto Sans SC',
|
||||
sans-serif;
|
||||
color: #111111;
|
||||
background:
|
||||
radial-gradient(circle at 12% 10%, rgba(255, 255, 255, 0.9), transparent 26%),
|
||||
radial-gradient(circle at 88% 18%, rgba(255, 255, 255, 0.74), transparent 22%),
|
||||
linear-gradient(150deg, #fbe9e8 0%, #f2d7d7 48%, #eed6d5 100%);
|
||||
color: #000000;
|
||||
background: #ffffff;
|
||||
--dashboard-width: 1072px;
|
||||
--dashboard-height: 1448px;
|
||||
--ink: #000000;
|
||||
--paper: #ffffff;
|
||||
--frame-stroke: #8b6b47;
|
||||
--frame-stroke-strong: #6f5235;
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
@@ -23,7 +26,9 @@ html,
|
||||
body,
|
||||
#app {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -39,45 +44,37 @@ img {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.75rem;
|
||||
padding: 0;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.page-shell--clock-face {
|
||||
background: #f5f2ef;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.dashboard-frame {
|
||||
width: 100%;
|
||||
aspect-ratio: 1024 / 600;
|
||||
padding: 1rem;
|
||||
border-radius: 1.9rem;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.42), rgba(255, 255, 255, 0.18)),
|
||||
rgba(255, 255, 255, 0.16);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.62),
|
||||
0 24px 46px rgba(99, 64, 66, 0.18);
|
||||
backdrop-filter: blur(14px);
|
||||
width: min(100vw, var(--dashboard-width));
|
||||
aspect-ratio: 1072 / 1448;
|
||||
background: var(--paper);
|
||||
}
|
||||
|
||||
.dashboard-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 0.95fr);
|
||||
grid-template-rows: minmax(0, 1fr) 72px;
|
||||
gap: 1rem;
|
||||
grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
|
||||
grid-template-rows: minmax(0, 1fr) 168px;
|
||||
gap: 1.25rem;
|
||||
height: 100%;
|
||||
padding: 1.4rem;
|
||||
align-content: start;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.card {
|
||||
min-height: 0;
|
||||
border-radius: 1.75rem;
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
box-shadow:
|
||||
0 16px 34px rgba(92, 67, 60, 0.18),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.72);
|
||||
border: 1px solid rgba(117, 80, 76, 0.08);
|
||||
border-radius: 2rem;
|
||||
background: var(--paper);
|
||||
border: 2px solid var(--frame-stroke);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.clock-face-stage {
|
||||
@@ -87,6 +84,12 @@ img {
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.page-shell {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
.dashboard-frame {
|
||||
aspect-ratio: auto;
|
||||
@@ -95,6 +98,7 @@ img {
|
||||
.dashboard-grid {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: none;
|
||||
padding: 0.9rem;
|
||||
}
|
||||
|
||||
.page-shell {
|
||||
|
||||
Reference in New Issue
Block a user