Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a43d929ec | ||
|
|
437bbadd0f | ||
|
|
d181ce5089 | ||
|
|
dab29e1cc0 | ||
|
|
3e6a02c29c | ||
|
|
16b87f3e07 | ||
|
|
fa56bb1c9c | ||
|
|
c7751251ac | ||
|
|
d2fc91f1f0 | ||
|
|
0379aa95dd | ||
|
|
6eb4f7707a | ||
|
|
3ee521b2a5 | ||
|
|
86367b6e33 | ||
|
|
730cf247f8 | ||
|
|
bbd9595352 | ||
|
|
0386e2fd9f | ||
|
|
2a8a61697f | ||
|
|
8c732ab38e | ||
|
|
2912c8b608 | ||
|
|
5ec34e89ca | ||
|
|
645e971b99 | ||
|
|
1764e4b590 | ||
|
|
75d8004d1b | ||
|
|
f653987369 | ||
|
|
28762f51c5 | ||
|
|
32d4a741f9 | ||
|
|
2f4221da56 | ||
|
|
60d8c0fd5f | ||
|
|
637f777135 | ||
|
|
a1b4c4f362 | ||
|
|
b6b24eef6c | ||
|
|
37dcab4db5 | ||
|
|
b9a769fdc4 | ||
|
|
2b127b6230 | ||
|
|
73ed977811 | ||
|
|
e81f0bb94b | ||
|
|
c3050a33f2 | ||
|
|
df9654fed9 | ||
|
|
239ae4ada2 | ||
|
|
33d50851b0 |
No files matched your search
@@ -94,11 +94,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn table_1080p() {
|
||||
// 45.85869386 ¢/s, rounded up.
|
||||
assert_eq!(cents(Some(RouterResolution::TenEightyP), 4), 184);
|
||||
assert_eq!(cents(Some(RouterResolution::TenEightyP), 5), 230);
|
||||
assert_eq!(cents(Some(RouterResolution::TenEightyP), 10), 459);
|
||||
assert_eq!(cents(Some(RouterResolution::TenEightyP), 30), 1376);
|
||||
// 61.62732357 ¢/s, rounded up.
|
||||
assert_eq!(cents(Some(RouterResolution::TenEightyP), 4), 247);
|
||||
assert_eq!(cents(Some(RouterResolution::TenEightyP), 5), 309);
|
||||
assert_eq!(cents(Some(RouterResolution::TenEightyP), 10), 617);
|
||||
assert_eq!(cents(Some(RouterResolution::TenEightyP), 30), 1849);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -121,16 +121,16 @@ mod tests {
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 30, Some(10)), 290);
|
||||
// 15.84362140 ¢/s × 40 = 633.74 → 634¢.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::SevenTwentyP), 30, Some(10)), 634);
|
||||
// 27.39973680 ¢/s × 40 = 1095.99 → 1096¢.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::TenEightyP), 30, Some(10)), 1096);
|
||||
// 38.49728574 ¢/s × 40 = 1539.89 → 1540¢.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::TenEightyP), 30, Some(10)), 1540);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_1080p_with_ten_input_seconds() {
|
||||
// 27.39973680 ¢/s over (output + 10) billed seconds, rounded up.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::TenEightyP), 5, Some(10)), 411);
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::TenEightyP), 10, Some(10)), 548);
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::TenEightyP), 30, Some(10)), 1096);
|
||||
// 38.49728574 ¢/s over (output + 10) billed seconds, rounded up.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::TenEightyP), 5, Some(10)), 578);
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::TenEightyP), 10, Some(10)), 770);
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::TenEightyP), 30, Some(10)), 1540);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -95,11 +95,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn table_1080p() {
|
||||
// 50.10486922 ¢/s, rounded up.
|
||||
assert_eq!(cents(Some(RouterResolution::TenEightyP), 4), 201);
|
||||
assert_eq!(cents(Some(RouterResolution::TenEightyP), 5), 251);
|
||||
assert_eq!(cents(Some(RouterResolution::TenEightyP), 10), 502);
|
||||
assert_eq!(cents(Some(RouterResolution::TenEightyP), 30), 1504);
|
||||
// 67.22980753 ¢/s, rounded up.
|
||||
assert_eq!(cents(Some(RouterResolution::TenEightyP), 4), 269);
|
||||
assert_eq!(cents(Some(RouterResolution::TenEightyP), 5), 337);
|
||||
assert_eq!(cents(Some(RouterResolution::TenEightyP), 10), 673);
|
||||
assert_eq!(cents(Some(RouterResolution::TenEightyP), 30), 2017);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -141,16 +141,16 @@ mod tests {
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 30, Some(10)), 343);
|
||||
// 18.72427984 ¢/s × 40 = 748.97 → 749¢.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::SevenTwentyP), 30, Some(10)), 749);
|
||||
// 29.93674947 ¢/s × 40 = 1197.47 → 1198¢.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::TenEightyP), 30, Some(10)), 1198);
|
||||
// 41.84487580 ¢/s × 40 = 1673.80 → 1674¢.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::TenEightyP), 30, Some(10)), 1674);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_1080p_with_ten_input_seconds() {
|
||||
// 29.93674947 ¢/s over (output + 10) billed seconds, rounded up.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::TenEightyP), 5, Some(10)), 450);
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::TenEightyP), 10, Some(10)), 599);
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::TenEightyP), 30, Some(10)), 1198);
|
||||
// 41.84487580 ¢/s over (output + 10) billed seconds, rounded up.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::TenEightyP), 5, Some(10)), 628);
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::TenEightyP), 10, Some(10)), 837);
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::TenEightyP), 30, Some(10)), 1674);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -137,14 +137,15 @@ pub fn seedance_2p5_preview_usd_cents(
|
||||
const SEEDANCE_2P5_CENTS_PER_SECOND_480P: f64 = 11.76954733;
|
||||
/// Seedance 2.5 — 720p price, USD cents per second.
|
||||
const SEEDANCE_2P5_CENTS_PER_SECOND_720P: f64 = 26.70781893;
|
||||
/// Seedance 2.5 — 1080p price, USD cents per second.
|
||||
const SEEDANCE_2P5_CENTS_PER_SECOND_1080P: f64 = 45.85869386;
|
||||
/// Seedance 2.5 — 1080p price, USD cents per second (updated 2026-09-17).
|
||||
const SEEDANCE_2P5_CENTS_PER_SECOND_1080P: f64 = 61.62732357;
|
||||
/// Seedance 2.5 — 480p price with video references, USD cents per second.
|
||||
const SEEDANCE_2P5_VIDEO_REFERENCE_CENTS_PER_SECOND_480P: f64 = 7.24279835;
|
||||
/// Seedance 2.5 — 720p price with video references, USD cents per second.
|
||||
const SEEDANCE_2P5_VIDEO_REFERENCE_CENTS_PER_SECOND_720P: f64 = 15.84362140;
|
||||
/// Seedance 2.5 — 1080p price with video references, USD cents per second.
|
||||
const SEEDANCE_2P5_VIDEO_REFERENCE_CENTS_PER_SECOND_1080P: f64 = 27.39973680;
|
||||
/// Seedance 2.5 — 1080p price with video references, USD cents per second
|
||||
/// (updated 2026-09-17).
|
||||
const SEEDANCE_2P5_VIDEO_REFERENCE_CENTS_PER_SECOND_1080P: f64 = 38.49728574;
|
||||
|
||||
/// ArtCraft's price (USD cents) for Seedance 2.5.
|
||||
///
|
||||
@@ -199,14 +200,15 @@ pub fn seedance_2p5_usd_cents(
|
||||
const SEEDANCE_2P5_ULTRA_CENTS_PER_SECOND_480P: f64 = 13.90946502;
|
||||
/// Seedance 2.5 Ultra — 720p price, USD cents per second.
|
||||
const SEEDANCE_2P5_ULTRA_CENTS_PER_SECOND_720P: f64 = 31.56378601;
|
||||
/// Seedance 2.5 Ultra — 1080p price, USD cents per second.
|
||||
const SEEDANCE_2P5_ULTRA_CENTS_PER_SECOND_1080P: f64 = 50.10486922;
|
||||
/// Seedance 2.5 Ultra — 1080p price, USD cents per second (updated 2026-09-17).
|
||||
const SEEDANCE_2P5_ULTRA_CENTS_PER_SECOND_1080P: f64 = 67.22980753;
|
||||
/// Seedance 2.5 Ultra — 480p price with video references, USD cents per second.
|
||||
const SEEDANCE_2P5_ULTRA_VIDEO_REFERENCE_CENTS_PER_SECOND_480P: f64 = 8.55967078;
|
||||
/// Seedance 2.5 Ultra — 720p price with video references, USD cents per second.
|
||||
const SEEDANCE_2P5_ULTRA_VIDEO_REFERENCE_CENTS_PER_SECOND_720P: f64 = 18.72427984;
|
||||
/// Seedance 2.5 Ultra — 1080p price with video references, USD cents per second.
|
||||
const SEEDANCE_2P5_ULTRA_VIDEO_REFERENCE_CENTS_PER_SECOND_1080P: f64 = 29.93674947;
|
||||
/// Seedance 2.5 Ultra — 1080p price with video references, USD cents per
|
||||
/// second (updated 2026-09-17).
|
||||
const SEEDANCE_2P5_ULTRA_VIDEO_REFERENCE_CENTS_PER_SECOND_1080P: f64 = 41.84487580;
|
||||
|
||||
/// ArtCraft's price (USD cents) for Seedance 2.5 Ultra.
|
||||
///
|
||||
|
||||
@@ -100,7 +100,7 @@ impl KinoviSeedance2p5CostState {
|
||||
// per-model credit rate at our bulk credit purchase rate).
|
||||
let costs = pricing_request.calculate_enterprise_costs();
|
||||
// 2.5 bills whole credits at 480p/720p, so the rounding below is exact
|
||||
// there; 1080p bills fractional credits (103.25/sec) and rounds to the
|
||||
// there; 1080p bills fractional credits (136.23/sec) and rounds to the
|
||||
// nearest credit. The USD cents (the authoritative charge) are rounded
|
||||
// up.
|
||||
let cost_in_credits = costs.kinovi_credits.round() as u64;
|
||||
@@ -125,7 +125,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
// ── Credits without video references (26/sec at 480p, 59/sec at 720p,
|
||||
// 103.25/sec at 1080p enterprise) ──
|
||||
// 136.23/sec at 1080p enterprise) ──
|
||||
|
||||
mod credits_without_video_references {
|
||||
use super::*;
|
||||
@@ -145,11 +145,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn credits_1080p() {
|
||||
// 103.25/sec enterprise; fractional totals round to the nearest credit
|
||||
// (516.25 → 516, 1032.5 → 1033, 3097.5 → 3098).
|
||||
assert_eq!(credits(Some(KinoviOutputResolution::TenEightyP), 5, false, None), 516);
|
||||
assert_eq!(credits(Some(KinoviOutputResolution::TenEightyP), 10, false, None), 1033);
|
||||
assert_eq!(credits(Some(KinoviOutputResolution::TenEightyP), 30, false, None), 3098);
|
||||
// 136.23/sec enterprise; fractional totals round to the nearest credit
|
||||
// (681.15 → 681, 1362.3 → 1362, 4086.9 → 4087).
|
||||
assert_eq!(credits(Some(KinoviOutputResolution::TenEightyP), 5, false, None), 681);
|
||||
assert_eq!(credits(Some(KinoviOutputResolution::TenEightyP), 10, false, None), 1362);
|
||||
assert_eq!(credits(Some(KinoviOutputResolution::TenEightyP), 30, false, None), 4087);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -164,7 +164,7 @@ mod tests {
|
||||
}
|
||||
|
||||
// ── Credits with video references (16/sec at 480p, 35/sec at 720p,
|
||||
// 61.69/sec at 1080p enterprise, over output duration + input seconds) ──
|
||||
// 81.4/sec at 1080p enterprise, over output duration + input seconds) ──
|
||||
|
||||
mod credits_with_video_references {
|
||||
use super::*;
|
||||
@@ -173,8 +173,8 @@ mod tests {
|
||||
fn thirty_second_output_with_ten_input_seconds_bills_forty() {
|
||||
assert_eq!(credits(Some(KinoviOutputResolution::FourEightyP), 30, true, Some(10)), 16 * 40);
|
||||
assert_eq!(credits(Some(KinoviOutputResolution::SevenTwentyP), 30, true, Some(10)), 35 * 40);
|
||||
// 61.69/sec enterprise × 40 = 2467.6 → 2468 credits.
|
||||
assert_eq!(credits(Some(KinoviOutputResolution::TenEightyP), 30, true, Some(10)), 2468);
|
||||
// 81.4/sec enterprise × 40 = 3256 credits.
|
||||
assert_eq!(credits(Some(KinoviOutputResolution::TenEightyP), 30, true, Some(10)), 3256);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -147,7 +147,7 @@ pub enum KinoviSeedance2p5OutputFormat {
|
||||
// |------------|----------------------|------------------------|
|
||||
// | 480p | 26 | (no discount) |
|
||||
// | 720p | 59 | (no discount) |
|
||||
// | 1080p | 107.55 | 103.25 |
|
||||
// | 1080p | 143.4 | 136.23 |
|
||||
//
|
||||
// With video references, the rate drops but the billed seconds are the
|
||||
// output duration PLUS the total seconds of reference video input
|
||||
@@ -160,11 +160,10 @@ pub enum KinoviSeedance2p5OutputFormat {
|
||||
// |------------|----------------------|------------------------|
|
||||
// | 480p | 16 | (no discount) |
|
||||
// | 720p | 35 | (no discount) |
|
||||
// | 1080p | 64.26 | 61.69 |
|
||||
// | 1080p | 85.68 | 81.4 |
|
||||
//
|
||||
// The 1080p enterprise rates were spot-checked against the Kinovi site
|
||||
// (2026-08-17): 4s / 10s / 30s without a reference video showed 413 /
|
||||
// 1,032.5 / 3,097.5 credits. 480p and 720p have no separately negotiated
|
||||
// The 1080p rates were last updated 2026-09-17 (previously 107.55 / 103.25
|
||||
// and 64.26 / 61.69 credits/sec). 480p and 720p have no separately negotiated
|
||||
// enterprise credit rate, so enterprise bills the same credits as consumer
|
||||
// (the tiers still convert to USD at their own purchase rates). Default
|
||||
// resolution (None) is 720p.
|
||||
@@ -180,8 +179,8 @@ const SEEDANCE_2P5_720P: KinoviPricingRate = KinoviPricingRate {
|
||||
maybe_enterprise_credits: None,
|
||||
};
|
||||
const SEEDANCE_2P5_1080P: KinoviPricingRate = KinoviPricingRate {
|
||||
consumer_credits: 107.55,
|
||||
maybe_enterprise_credits: Some(103.25),
|
||||
consumer_credits: 143.4,
|
||||
maybe_enterprise_credits: Some(136.23),
|
||||
};
|
||||
|
||||
/// Per-second credit rates when video references are attached. These REPLACE
|
||||
@@ -196,8 +195,8 @@ const SEEDANCE_2P5_720P_VIDEO_REF: KinoviPricingRate = KinoviPricingRate {
|
||||
maybe_enterprise_credits: None,
|
||||
};
|
||||
const SEEDANCE_2P5_1080P_VIDEO_REF: KinoviPricingRate = KinoviPricingRate {
|
||||
consumer_credits: 64.26,
|
||||
maybe_enterprise_credits: Some(61.69),
|
||||
consumer_credits: 85.68,
|
||||
maybe_enterprise_credits: Some(81.4),
|
||||
};
|
||||
|
||||
impl KinoviCostCalculatorTrait for GenerateSeedance2p5Request {
|
||||
@@ -375,7 +374,7 @@ mod tests {
|
||||
// ── No video references: output-duration billing ──
|
||||
//
|
||||
// 480p = 26 credits/sec and 720p = 59 credits/sec at both tiers (no
|
||||
// negotiated enterprise discount); 1080p = 107.55 consumer / 103.25
|
||||
// negotiated enterprise discount); 1080p = 143.4 consumer / 136.23
|
||||
// enterprise credits/sec.
|
||||
|
||||
mod without_video_references {
|
||||
@@ -401,24 +400,23 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn table_1080p() {
|
||||
// Enterprise 103.25 credits/sec — the 4s / 10s / 30s values were
|
||||
// verified against the Kinovi site (413 / 1,032.5 / 3,097.5).
|
||||
assert_eq!(t2v_1080(4).calculate_enterprise_costs().kinovi_credits, 413.0);
|
||||
assert_eq!(t2v_1080(5).calculate_enterprise_costs().kinovi_credits, 516.25);
|
||||
assert_eq!(t2v_1080(10).calculate_enterprise_costs().kinovi_credits, 1032.5);
|
||||
assert_eq!(t2v_1080(15).calculate_enterprise_costs().kinovi_credits, 1548.75);
|
||||
assert_eq!(t2v_1080(20).calculate_enterprise_costs().kinovi_credits, 2065.0);
|
||||
assert_eq!(t2v_1080(25).calculate_enterprise_costs().kinovi_credits, 2581.25);
|
||||
assert_eq!(t2v_1080(30).calculate_enterprise_costs().kinovi_credits, 3097.5);
|
||||
// Enterprise 136.23 credits/sec (2026-09-17).
|
||||
assert_eq!(t2v_1080(4).calculate_enterprise_costs().kinovi_credits, 544.92);
|
||||
assert_eq!(t2v_1080(5).calculate_enterprise_costs().kinovi_credits, 681.15);
|
||||
assert_eq!(t2v_1080(10).calculate_enterprise_costs().kinovi_credits, 1362.3);
|
||||
assert_eq!(t2v_1080(15).calculate_enterprise_costs().kinovi_credits, 2043.45);
|
||||
assert_eq!(t2v_1080(20).calculate_enterprise_costs().kinovi_credits, 2724.6);
|
||||
assert_eq!(t2v_1080(25).calculate_enterprise_costs().kinovi_credits, 3405.75);
|
||||
assert_eq!(t2v_1080(30).calculate_enterprise_costs().kinovi_credits, 4086.9);
|
||||
|
||||
// Consumer 107.55 credits/sec.
|
||||
assert_eq!(t2v_1080(4).calculate_consumer_costs().kinovi_credits, 430.2);
|
||||
assert_eq!(t2v_1080(5).calculate_consumer_costs().kinovi_credits, 537.75);
|
||||
assert_eq!(t2v_1080(10).calculate_consumer_costs().kinovi_credits, 1075.5);
|
||||
assert_eq!(t2v_1080(15).calculate_consumer_costs().kinovi_credits, 1613.25);
|
||||
assert_eq!(t2v_1080(20).calculate_consumer_costs().kinovi_credits, 2151.0);
|
||||
assert_eq!(t2v_1080(25).calculate_consumer_costs().kinovi_credits, 2688.75);
|
||||
assert_eq!(t2v_1080(30).calculate_consumer_costs().kinovi_credits, 3226.5);
|
||||
// Consumer 143.4 credits/sec.
|
||||
assert_eq!(t2v_1080(4).calculate_consumer_costs().kinovi_credits, 573.6);
|
||||
assert_eq!(t2v_1080(5).calculate_consumer_costs().kinovi_credits, 717.0);
|
||||
assert_eq!(t2v_1080(10).calculate_consumer_costs().kinovi_credits, 1434.0);
|
||||
assert_eq!(t2v_1080(15).calculate_consumer_costs().kinovi_credits, 2151.0);
|
||||
assert_eq!(t2v_1080(20).calculate_consumer_costs().kinovi_credits, 2868.0);
|
||||
assert_eq!(t2v_1080(25).calculate_consumer_costs().kinovi_credits, 3585.0);
|
||||
assert_eq!(t2v_1080(30).calculate_consumer_costs().kinovi_credits, 4302.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -456,7 +454,7 @@ mod tests {
|
||||
|
||||
let mut request_1080 = keyframe_request(10);
|
||||
request_1080.output_resolution = Some(KinoviSeedance2p5OutputResolution::TenEightyP);
|
||||
assert_eq!(request_1080.calculate_enterprise_costs().kinovi_credits, 1032.5);
|
||||
assert_eq!(request_1080.calculate_enterprise_costs().kinovi_credits, 1362.3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -489,7 +487,7 @@ mod tests {
|
||||
// ── With video references: input seconds are billed too ──
|
||||
//
|
||||
// 480p = 16 credits/sec and 720p = 35 credits/sec at both tiers; 1080p
|
||||
// = 64.26 consumer / 61.69 enterprise credits/sec. All over
|
||||
// = 85.68 consumer / 81.4 enterprise credits/sec. All over
|
||||
// (output duration + total_input_seconds) billed seconds.
|
||||
|
||||
mod with_video_references {
|
||||
@@ -541,21 +539,21 @@ mod tests {
|
||||
// seconds: billed seconds = 20 + clamp(input, 4, 30). E.g. a 10s
|
||||
// input video + 20s generation = 30 billed seconds.
|
||||
//
|
||||
// Enterprise 61.69 credits/sec.
|
||||
assert_eq!(video_ref_1080(20, Some(1)).calculate_enterprise_costs().kinovi_credits, 1480.56);
|
||||
assert_eq!(video_ref_1080(20, Some(5)).calculate_enterprise_costs().kinovi_credits, 1542.25);
|
||||
assert_eq!(video_ref_1080(20, Some(10)).calculate_enterprise_costs().kinovi_credits, 1850.7);
|
||||
assert_eq!(video_ref_1080(20, Some(15)).calculate_enterprise_costs().kinovi_credits, 2159.15);
|
||||
assert_eq!(video_ref_1080(20, Some(20)).calculate_enterprise_costs().kinovi_credits, 2467.6);
|
||||
assert_eq!(video_ref_1080(20, Some(30)).calculate_enterprise_costs().kinovi_credits, 3084.5);
|
||||
// Enterprise 81.4 credits/sec.
|
||||
assert_eq!(video_ref_1080(20, Some(1)).calculate_enterprise_costs().kinovi_credits, 1953.6);
|
||||
assert_eq!(video_ref_1080(20, Some(5)).calculate_enterprise_costs().kinovi_credits, 2035.0);
|
||||
assert_eq!(video_ref_1080(20, Some(10)).calculate_enterprise_costs().kinovi_credits, 2442.0);
|
||||
assert_eq!(video_ref_1080(20, Some(15)).calculate_enterprise_costs().kinovi_credits, 2849.0);
|
||||
assert_eq!(video_ref_1080(20, Some(20)).calculate_enterprise_costs().kinovi_credits, 3256.0);
|
||||
assert_eq!(video_ref_1080(20, Some(30)).calculate_enterprise_costs().kinovi_credits, 4070.0);
|
||||
|
||||
// Consumer 64.26 credits/sec.
|
||||
assert_eq!(video_ref_1080(20, Some(1)).calculate_consumer_costs().kinovi_credits, 1542.24);
|
||||
assert_eq!(video_ref_1080(20, Some(5)).calculate_consumer_costs().kinovi_credits, 1606.5);
|
||||
assert_eq!(video_ref_1080(20, Some(10)).calculate_consumer_costs().kinovi_credits, 1927.8);
|
||||
assert_eq!(video_ref_1080(20, Some(15)).calculate_consumer_costs().kinovi_credits, 2249.1);
|
||||
assert_eq!(video_ref_1080(20, Some(20)).calculate_consumer_costs().kinovi_credits, 2570.4);
|
||||
assert_eq!(video_ref_1080(20, Some(30)).calculate_consumer_costs().kinovi_credits, 3213.0);
|
||||
// Consumer 85.68 credits/sec.
|
||||
assert_eq!(video_ref_1080(20, Some(1)).calculate_consumer_costs().kinovi_credits, 2056.32);
|
||||
assert_eq!(video_ref_1080(20, Some(5)).calculate_consumer_costs().kinovi_credits, 2142.0);
|
||||
assert_eq!(video_ref_1080(20, Some(10)).calculate_consumer_costs().kinovi_credits, 2570.4);
|
||||
assert_eq!(video_ref_1080(20, Some(15)).calculate_consumer_costs().kinovi_credits, 2998.8);
|
||||
assert_eq!(video_ref_1080(20, Some(20)).calculate_consumer_costs().kinovi_credits, 3427.2);
|
||||
assert_eq!(video_ref_1080(20, Some(30)).calculate_consumer_costs().kinovi_credits, 4284.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -574,9 +572,9 @@ mod tests {
|
||||
// 30-second maximum so the estimate never undershoots the charge.
|
||||
assert_eq!(video_ref_480(10, None).calculate_consumer_costs().kinovi_credits, 640.0);
|
||||
assert_eq!(video_ref_720(10, None).calculate_consumer_costs().kinovi_credits, 1400.0);
|
||||
// 1080p: 40 billed seconds at 61.69 enterprise / 64.26 consumer.
|
||||
assert_eq!(video_ref_1080(10, None).calculate_enterprise_costs().kinovi_credits, 2467.6);
|
||||
assert_eq!(video_ref_1080(10, None).calculate_consumer_costs().kinovi_credits, 2570.4);
|
||||
// 1080p: 40 billed seconds at 81.4 enterprise / 85.68 consumer.
|
||||
assert_eq!(video_ref_1080(10, None).calculate_enterprise_costs().kinovi_credits, 3256.0);
|
||||
assert_eq!(video_ref_1080(10, None).calculate_consumer_costs().kinovi_credits, 3427.2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -649,47 +647,47 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn enterprise_cents_1080p_4s() {
|
||||
// 413 credits; 41300/243.16 = 169.8470 cents.
|
||||
// 544.92 credits; 54492/243.16 = 224.0994 cents.
|
||||
let cost = t2v_1080(4).calculate_enterprise_costs();
|
||||
assert_eq!(cost.usd_cents_rounded_up, 170);
|
||||
assert_eq!(cost.usd_cents_rounded_down, 169);
|
||||
assert!((cost.usd_cents_fractional - 169.8470).abs() < FLOAT_TOLERANCE);
|
||||
assert_eq!(cost.usd_cents_rounded_up, 225);
|
||||
assert_eq!(cost.usd_cents_rounded_down, 224);
|
||||
assert!((cost.usd_cents_fractional - 224.0994).abs() < FLOAT_TOLERANCE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn consumer_cents_1080p_4s() {
|
||||
// 430.2 credits; 43020/192.98 = 222.9247 cents.
|
||||
// 573.6 credits; 57360/192.98 = 297.2329 cents.
|
||||
let cost = t2v_1080(4).calculate_consumer_costs();
|
||||
assert_eq!(cost.usd_cents_rounded_up, 223);
|
||||
assert_eq!(cost.usd_cents_rounded_down, 222);
|
||||
assert!((cost.usd_cents_fractional - 222.9247).abs() < FLOAT_TOLERANCE);
|
||||
assert_eq!(cost.usd_cents_rounded_up, 298);
|
||||
assert_eq!(cost.usd_cents_rounded_down, 297);
|
||||
assert!((cost.usd_cents_fractional - 297.2329).abs() < FLOAT_TOLERANCE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enterprise_cents_1080p_30s() {
|
||||
// 3097.5 credits; 309750/243.16 = 1273.8526 cents.
|
||||
// 4086.9 credits; 408690/243.16 = 1680.7452 cents.
|
||||
let cost = t2v_1080(30).calculate_enterprise_costs();
|
||||
assert_eq!(cost.usd_cents_rounded_up, 1274);
|
||||
assert_eq!(cost.usd_cents_rounded_down, 1273);
|
||||
assert!((cost.usd_cents_fractional - 1273.8526).abs() < FLOAT_TOLERANCE);
|
||||
assert_eq!(cost.usd_cents_rounded_up, 1681);
|
||||
assert_eq!(cost.usd_cents_rounded_down, 1680);
|
||||
assert!((cost.usd_cents_fractional - 1680.7452).abs() < FLOAT_TOLERANCE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enterprise_cents_1080p_video_ref_30_billed_seconds() {
|
||||
// 61.69 × 30 = 1850.7 credits; 185070/243.16 = 761.1038 cents.
|
||||
// 81.4 × 30 = 2442 credits; 244200/243.16 = 1004.2770 cents.
|
||||
let cost = video_ref_1080(20, Some(10)).calculate_enterprise_costs();
|
||||
assert_eq!(cost.usd_cents_rounded_up, 762);
|
||||
assert_eq!(cost.usd_cents_rounded_down, 761);
|
||||
assert!((cost.usd_cents_fractional - 761.1038).abs() < FLOAT_TOLERANCE);
|
||||
assert_eq!(cost.usd_cents_rounded_up, 1005);
|
||||
assert_eq!(cost.usd_cents_rounded_down, 1004);
|
||||
assert!((cost.usd_cents_fractional - 1004.2770).abs() < FLOAT_TOLERANCE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn consumer_cents_1080p_video_ref_30_billed_seconds() {
|
||||
// 64.26 × 30 = 1927.8 credits; 192780/192.98 = 998.9636 cents.
|
||||
// 85.68 × 30 = 2570.4 credits; 257040/192.98 = 1331.9515 cents.
|
||||
let cost = video_ref_1080(20, Some(10)).calculate_consumer_costs();
|
||||
assert_eq!(cost.usd_cents_rounded_up, 999);
|
||||
assert_eq!(cost.usd_cents_rounded_down, 998);
|
||||
assert!((cost.usd_cents_fractional - 998.9636).abs() < FLOAT_TOLERANCE);
|
||||
assert_eq!(cost.usd_cents_rounded_up, 1332);
|
||||
assert_eq!(cost.usd_cents_rounded_down, 1331);
|
||||
assert!((cost.usd_cents_fractional - 1331.9515).abs() < FLOAT_TOLERANCE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -700,7 +698,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn video_reference_rate_is_cheaper_per_second() {
|
||||
// 16 < 26, 35 < 59, and 61.69 < 103.25: the with-references rate is
|
||||
// 16 < 26, 35 < 59, and 81.4 < 136.23: the with-references rate is
|
||||
// lower per billed second (the input seconds are where the money
|
||||
// goes). Compare at the minimum billed input (1s clamps to 4) —
|
||||
// unknown or zero input assumes the 30-second maximum, which would
|
||||
|
||||
@@ -75,7 +75,10 @@ pub fn hydrate_to_router_request(
|
||||
},
|
||||
duration_seconds: request.duration_seconds,
|
||||
video_batch_count: request.video_batch_count,
|
||||
generate_audio: request.generate_audio,
|
||||
// Seedance 2.0 / 2.5 always generate sound. Kinovi honours an explicit
|
||||
// `generate_audio: false`, and the frontend was sending one, so the flag
|
||||
// is dropped here and never reaches Kinovi for these models.
|
||||
generate_audio: if is_seedance_2x(&model) { None } else { request.generate_audio },
|
||||
// Set by the handler after probing reference video durations (only
|
||||
// models that bill input seconds use it).
|
||||
total_reference_video_input_seconds: None,
|
||||
@@ -84,6 +87,24 @@ pub fn hydrate_to_router_request(
|
||||
})
|
||||
}
|
||||
|
||||
fn is_seedance_2x(model: &RouterVideoModel) -> bool {
|
||||
matches!(
|
||||
model,
|
||||
RouterVideoModel::Seedance2p0
|
||||
| RouterVideoModel::Seedance2p0Fast
|
||||
| RouterVideoModel::Seedance2p0Mini
|
||||
| RouterVideoModel::Seedance2p0BytePlus
|
||||
| RouterVideoModel::Seedance2p0BytePlusFast
|
||||
| RouterVideoModel::Seedance2p0BytePlusMini
|
||||
| RouterVideoModel::Seedance2p0BytePlusUltra
|
||||
| RouterVideoModel::Seedance2p0BytePlusUltraFast
|
||||
| RouterVideoModel::Seedance2p0BytePlusUltraMini
|
||||
| RouterVideoModel::Seedance2p5Preview
|
||||
| RouterVideoModel::Seedance2p5
|
||||
| RouterVideoModel::Seedance2p5Ultra
|
||||
)
|
||||
}
|
||||
|
||||
fn convert_model(
|
||||
model: &CommonVideoModelEnum,
|
||||
) -> Result<RouterVideoModel, CommonWebError> {
|
||||
@@ -144,7 +165,7 @@ mod tests {
|
||||
use serde_json::{json, Value};
|
||||
|
||||
#[tokio::test]
|
||||
async fn artcraft_http_request_reaches_kinovi_with_format_and_audio() {
|
||||
async fn artcraft_http_request_reaches_kinovi_with_format_and_without_audio_flag() {
|
||||
// No media inputs or send: finalization needs a client but makes no HTTP calls.
|
||||
let client = RouterClient::KinoviWeb(RouterKinoviWebClient::new(
|
||||
KinoviWebSession::from_cookies_string(String::new()),
|
||||
@@ -183,7 +204,7 @@ mod tests {
|
||||
});
|
||||
let expected_format = output_format.as_ref().and_then(Value::as_str).unwrap_or("mp4");
|
||||
assert_eq!(actual_format, Some(expected_format), "model: {model}");
|
||||
assert_eq!(request.maybe_generate_audio, generate_audio);
|
||||
assert_eq!(request.maybe_generate_audio, None, "generate_audio: {generate_audio:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,6 +231,44 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
mod generate_audio {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn seedance_2x_drops_generate_audio() {
|
||||
for model in [
|
||||
CommonVideoModelEnum::Seedance2p0,
|
||||
CommonVideoModelEnum::Seedance2p0Fast,
|
||||
CommonVideoModelEnum::Seedance2p0Mini,
|
||||
CommonVideoModelEnum::Seedance2p5,
|
||||
CommonVideoModelEnum::Seedance2p5Ultra,
|
||||
] {
|
||||
for generate_audio in [None, Some(true), Some(false)] {
|
||||
let request = OmniGenVideoCostAndGenerateRequest {
|
||||
model: Some(model),
|
||||
generate_audio,
|
||||
..base_request()
|
||||
};
|
||||
let builder = hydrate_to_router_request(&request).expect("hydrate should succeed");
|
||||
assert_eq!(builder.generate_audio, None, "model: {model:?}, generate_audio: {generate_audio:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn other_models_keep_generate_audio() {
|
||||
for generate_audio in [None, Some(true), Some(false)] {
|
||||
let request = OmniGenVideoCostAndGenerateRequest {
|
||||
model: Some(CommonVideoModelEnum::Veo3p1),
|
||||
generate_audio,
|
||||
..base_request()
|
||||
};
|
||||
let builder = hydrate_to_router_request(&request).expect("hydrate should succeed");
|
||||
assert_eq!(builder.generate_audio, generate_audio);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod bitrate_hydration {
|
||||
use super::*;
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ use enums::common::generation::common_video_model::CommonVideoModel;
|
||||
use artcraft_api_keys::ArtcraftApiKey;
|
||||
|
||||
use crate::http_server::endpoints::omni_gen::generate::video::tests::support::{
|
||||
base_generate_request, to_omni_api_request, ExpectedCredits, Seconds, TestHarness,
|
||||
STARTING_CREDITS,
|
||||
base_generate_request, stub_traffic, to_omni_api_request, ExpectedCredits, Seconds,
|
||||
TestHarness, STARTING_CREDITS,
|
||||
};
|
||||
|
||||
mod billing_parity {
|
||||
@@ -46,6 +46,26 @@ mod billing_parity {
|
||||
}
|
||||
}
|
||||
|
||||
mod generate_audio {
|
||||
use super::*;
|
||||
|
||||
/// Seedance 2.0 / 2.5 always produce sound. Even when a caller explicitly
|
||||
/// sends `generate_audio: false`, neither endpoint forwards the flag to
|
||||
/// Kinovi — the key must be absent from the captured request body so Kinovi
|
||||
/// falls back to its default of sound on.
|
||||
#[tokio::test]
|
||||
#[cfg_attr(feature = "skip_database_tests", ignore)]
|
||||
async fn seedance_2p0_never_forwards_generate_audio_on_either_endpoint() {
|
||||
assert_endpoints_omit_generate_audio(CommonVideoModel::Seedance2p0).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(feature = "skip_database_tests", ignore)]
|
||||
async fn seedance_2p5_never_forwards_generate_audio_on_either_endpoint() {
|
||||
assert_endpoints_omit_generate_audio(CommonVideoModel::Seedance2p5).await;
|
||||
}
|
||||
}
|
||||
|
||||
mod url_ingestion {
|
||||
use super::*;
|
||||
|
||||
@@ -195,3 +215,47 @@ async fn assert_endpoints_bill_identically(
|
||||
"{:?}: omni_api debited the wrong amount", model,
|
||||
);
|
||||
}
|
||||
|
||||
/// Run `model` with an explicit `generate_audio: false` through omni_gen
|
||||
/// (session cookie) and omni_api (API key); assert both succeed and neither
|
||||
/// captured Kinovi request carries a `generate_audio` key.
|
||||
async fn assert_endpoints_omit_generate_audio(model: CommonVideoModel) {
|
||||
let harness = TestHarness::create().await;
|
||||
|
||||
// omni_gen, session-cookie user.
|
||||
let web_user = harness.create_funded_user(STARTING_CREDITS).await;
|
||||
let mut request = base_generate_request(model);
|
||||
let web_prompt = format!("audio flag test web {}", request.idempotency_token.as_deref().unwrap());
|
||||
request.prompt = Some(web_prompt.clone());
|
||||
request.generate_audio = Some(false);
|
||||
let response = harness
|
||||
.post_generate(&web_user, request)
|
||||
.await
|
||||
.unwrap_or_else(|err| panic!("{:?}: omni_gen generation failed: {:?}", model, err));
|
||||
assert!(response.success);
|
||||
|
||||
// omni_api, API-key user.
|
||||
let api_user = harness.create_funded_user(STARTING_CREDITS).await;
|
||||
let api_key = harness.create_api_key(&api_user).await;
|
||||
let mut request = base_generate_request(model);
|
||||
let api_prompt = format!("audio flag test api {}", request.idempotency_token.as_deref().unwrap());
|
||||
request.prompt = Some(api_prompt.clone());
|
||||
request.generate_audio = Some(false);
|
||||
let response = harness
|
||||
.post_generate_via_api_key(&api_key, request)
|
||||
.await
|
||||
.unwrap_or_else(|err| panic!("{:?}: omni_api generation failed: {:?}", model, err));
|
||||
assert!(response.success);
|
||||
|
||||
let traffic = stub_traffic().lock().unwrap();
|
||||
for (endpoint, prompt) in [("omni_gen", &web_prompt), ("omni_api", &api_prompt)] {
|
||||
let params = traffic.workflows.iter()
|
||||
.map(|request| &request["0"]["json"]["apiParams"])
|
||||
.find(|params| params["prompt"].as_str() == Some(prompt))
|
||||
.unwrap_or_else(|| panic!("{:?}: no captured Kinovi request for {}", model, endpoint));
|
||||
assert!(
|
||||
params.get("generate_audio").is_none(),
|
||||
"{:?}: {} forwarded generate_audio to Kinovi: {}", model, endpoint, params,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,8 @@ async fn quicktime_uploads_are_video_references_with_mp4_or_mov_output() {
|
||||
.find(|params| params["prompt"].as_str() == Some(&prompt))
|
||||
.expect("captured Kinovi request");
|
||||
assert_eq!(params["output_format"], expected);
|
||||
assert_eq!(params["generate_audio"], true);
|
||||
// Seedance 2.x never forwards the audio flag; Kinovi defaults to sound on.
|
||||
assert!(params.get("generate_audio").is_none(), "{}", params);
|
||||
assert_eq!(params["mode"], "reference");
|
||||
assert!(params["videoUrls"][0].as_str().is_some_and(|url| url.ends_with(".mov")), "{}", params);
|
||||
assert_eq!(traffic.uploads.get("fixture.mov").unwrap(), &bytes);
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
//!
|
||||
//! Rate cards (ceil-rounded once):
|
||||
//! - Seedance 2.5: 480p 11.76954733 ¢/s, 720p 26.70781893 ¢/s,
|
||||
//! 1080p 45.85869386 ¢/s; with video refs 480p 7.24279835 ¢/s,
|
||||
//! 720p 15.84362140 ¢/s, 1080p 27.39973680 ¢/s over (output + input)
|
||||
//! 1080p 61.62732357 ¢/s; with video refs 480p 7.24279835 ¢/s,
|
||||
//! 720p 15.84362140 ¢/s, 1080p 38.49728574 ¢/s over (output + input)
|
||||
//! seconds
|
||||
//! - Seedance 2.5 Ultra: 480p 13.90946502 ¢/s, 720p 31.56378601 ¢/s,
|
||||
//! 1080p 50.10486922 ¢/s; with video refs 480p 8.55967078 ¢/s,
|
||||
//! 720p 18.72427984 ¢/s, 1080p 29.93674947 ¢/s
|
||||
//! 1080p 67.22980753 ¢/s; with video refs 480p 8.55967078 ¢/s,
|
||||
//! 720p 18.72427984 ¢/s, 1080p 41.84487580 ¢/s
|
||||
//!
|
||||
//! Both models offer 480p/720p/1080p (4K downgrades to 1080p), durations
|
||||
//! run 4-30s, and every request generates a single video regardless of the
|
||||
@@ -90,60 +90,60 @@ mod seedance_2p5 {
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(28), Batch(1), ExpectedCredits(748)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(29), Batch(1), ExpectedCredits(775)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(30), Batch(1), ExpectedCredits(802)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(4), Batch(1), ExpectedCredits(184)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(1), ExpectedCredits(276)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(1), ExpectedCredits(322)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(8), Batch(1), ExpectedCredits(367)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(1), ExpectedCredits(413)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(10), Batch(1), ExpectedCredits(459)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(1), ExpectedCredits(505)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(12), Batch(1), ExpectedCredits(551)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(1), ExpectedCredits(597)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(14), Batch(1), ExpectedCredits(643)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(1), ExpectedCredits(688)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(16), Batch(1), ExpectedCredits(734)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(17), Batch(1), ExpectedCredits(780)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(18), Batch(1), ExpectedCredits(826)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(19), Batch(1), ExpectedCredits(872)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(20), Batch(1), ExpectedCredits(918)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(21), Batch(1), ExpectedCredits(964)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(22), Batch(1), ExpectedCredits(1009)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(23), Batch(1), ExpectedCredits(1055)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(24), Batch(1), ExpectedCredits(1101)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(25), Batch(1), ExpectedCredits(1147)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(26), Batch(1), ExpectedCredits(1193)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(27), Batch(1), ExpectedCredits(1239)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(28), Batch(1), ExpectedCredits(1285)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(29), Batch(1), ExpectedCredits(1330)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(30), Batch(1), ExpectedCredits(1376)),
|
||||
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(184)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(276)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(322)),
|
||||
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(367)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(413)),
|
||||
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(459)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(505)),
|
||||
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(551)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(597)),
|
||||
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(643)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(688)),
|
||||
(Some(CommonResolution::FourK), Seconds(16), Batch(1), ExpectedCredits(734)),
|
||||
(Some(CommonResolution::FourK), Seconds(17), Batch(1), ExpectedCredits(780)),
|
||||
(Some(CommonResolution::FourK), Seconds(18), Batch(1), ExpectedCredits(826)),
|
||||
(Some(CommonResolution::FourK), Seconds(19), Batch(1), ExpectedCredits(872)),
|
||||
(Some(CommonResolution::FourK), Seconds(20), Batch(1), ExpectedCredits(918)),
|
||||
(Some(CommonResolution::FourK), Seconds(21), Batch(1), ExpectedCredits(964)),
|
||||
(Some(CommonResolution::FourK), Seconds(22), Batch(1), ExpectedCredits(1009)),
|
||||
(Some(CommonResolution::FourK), Seconds(23), Batch(1), ExpectedCredits(1055)),
|
||||
(Some(CommonResolution::FourK), Seconds(24), Batch(1), ExpectedCredits(1101)),
|
||||
(Some(CommonResolution::FourK), Seconds(25), Batch(1), ExpectedCredits(1147)),
|
||||
(Some(CommonResolution::FourK), Seconds(26), Batch(1), ExpectedCredits(1193)),
|
||||
(Some(CommonResolution::FourK), Seconds(27), Batch(1), ExpectedCredits(1239)),
|
||||
(Some(CommonResolution::FourK), Seconds(28), Batch(1), ExpectedCredits(1285)),
|
||||
(Some(CommonResolution::FourK), Seconds(29), Batch(1), ExpectedCredits(1330)),
|
||||
(Some(CommonResolution::FourK), Seconds(30), Batch(1), ExpectedCredits(1376)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(4), Batch(1), ExpectedCredits(247)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(1), ExpectedCredits(370)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(1), ExpectedCredits(432)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(8), Batch(1), ExpectedCredits(494)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(1), ExpectedCredits(555)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(10), Batch(1), ExpectedCredits(617)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(1), ExpectedCredits(678)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(12), Batch(1), ExpectedCredits(740)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(1), ExpectedCredits(802)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(14), Batch(1), ExpectedCredits(863)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(1), ExpectedCredits(925)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(16), Batch(1), ExpectedCredits(987)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(17), Batch(1), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(18), Batch(1), ExpectedCredits(1110)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(19), Batch(1), ExpectedCredits(1171)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(20), Batch(1), ExpectedCredits(1233)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(21), Batch(1), ExpectedCredits(1295)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(22), Batch(1), ExpectedCredits(1356)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(23), Batch(1), ExpectedCredits(1418)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(24), Batch(1), ExpectedCredits(1480)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(25), Batch(1), ExpectedCredits(1541)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(26), Batch(1), ExpectedCredits(1603)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(27), Batch(1), ExpectedCredits(1664)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(28), Batch(1), ExpectedCredits(1726)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(29), Batch(1), ExpectedCredits(1788)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(30), Batch(1), ExpectedCredits(1849)),
|
||||
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(247)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(370)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(432)),
|
||||
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(494)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(555)),
|
||||
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(617)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(678)),
|
||||
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(740)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(802)),
|
||||
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(863)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(925)),
|
||||
(Some(CommonResolution::FourK), Seconds(16), Batch(1), ExpectedCredits(987)),
|
||||
(Some(CommonResolution::FourK), Seconds(17), Batch(1), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::FourK), Seconds(18), Batch(1), ExpectedCredits(1110)),
|
||||
(Some(CommonResolution::FourK), Seconds(19), Batch(1), ExpectedCredits(1171)),
|
||||
(Some(CommonResolution::FourK), Seconds(20), Batch(1), ExpectedCredits(1233)),
|
||||
(Some(CommonResolution::FourK), Seconds(21), Batch(1), ExpectedCredits(1295)),
|
||||
(Some(CommonResolution::FourK), Seconds(22), Batch(1), ExpectedCredits(1356)),
|
||||
(Some(CommonResolution::FourK), Seconds(23), Batch(1), ExpectedCredits(1418)),
|
||||
(Some(CommonResolution::FourK), Seconds(24), Batch(1), ExpectedCredits(1480)),
|
||||
(Some(CommonResolution::FourK), Seconds(25), Batch(1), ExpectedCredits(1541)),
|
||||
(Some(CommonResolution::FourK), Seconds(26), Batch(1), ExpectedCredits(1603)),
|
||||
(Some(CommonResolution::FourK), Seconds(27), Batch(1), ExpectedCredits(1664)),
|
||||
(Some(CommonResolution::FourK), Seconds(28), Batch(1), ExpectedCredits(1726)),
|
||||
(Some(CommonResolution::FourK), Seconds(29), Batch(1), ExpectedCredits(1788)),
|
||||
(Some(CommonResolution::FourK), Seconds(30), Batch(1), ExpectedCredits(1849)),
|
||||
];
|
||||
|
||||
for (resolution, seconds, batch, expected) in cases {
|
||||
@@ -182,26 +182,26 @@ mod seedance_2p5 {
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(8), ExpectedCredits(134)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(9), ExpectedCredits(134)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(10), ExpectedCredits(134)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(2), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(4), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(5), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(6), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(7), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(8), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(9), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(2), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(4), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(5), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(6), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(7), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(8), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(9), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(309)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(309)),
|
||||
];
|
||||
|
||||
for (resolution, seconds, batch, expected) in cases {
|
||||
@@ -230,18 +230,18 @@ mod seedance_2p5 {
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(5), ExpectedCredits(294)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(8), ExpectedCredits(348)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(10), ExpectedCredits(401)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(2), ExpectedCredits(276)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(322)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(4), ExpectedCredits(413)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(505)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(597)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(688)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(276)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(322)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(413)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(505)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(597)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(688)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(2), ExpectedCredits(370)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(432)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(4), ExpectedCredits(555)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(678)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(802)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(925)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(370)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(432)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(555)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(678)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(802)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(925)),
|
||||
];
|
||||
|
||||
for (resolution, seconds, batch, expected) in cases {
|
||||
@@ -313,60 +313,60 @@ mod seedance_2p5 {
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(28), Batch(1), ExpectedCredits(919)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(29), Batch(1), ExpectedCredits(935)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(30), Batch(1), ExpectedCredits(951)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(4), Batch(1), ExpectedCredits(932)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(1), ExpectedCredits(987)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(1), ExpectedCredits(1014)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(8), Batch(1), ExpectedCredits(1042)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(1), ExpectedCredits(1069)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(10), Batch(1), ExpectedCredits(1096)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(1), ExpectedCredits(1124)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(12), Batch(1), ExpectedCredits(1151)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(1), ExpectedCredits(1179)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(14), Batch(1), ExpectedCredits(1206)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(1), ExpectedCredits(1233)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(16), Batch(1), ExpectedCredits(1261)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(17), Batch(1), ExpectedCredits(1288)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(18), Batch(1), ExpectedCredits(1316)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(19), Batch(1), ExpectedCredits(1343)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(20), Batch(1), ExpectedCredits(1370)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(21), Batch(1), ExpectedCredits(1398)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(22), Batch(1), ExpectedCredits(1425)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(23), Batch(1), ExpectedCredits(1453)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(24), Batch(1), ExpectedCredits(1480)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(25), Batch(1), ExpectedCredits(1507)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(26), Batch(1), ExpectedCredits(1535)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(27), Batch(1), ExpectedCredits(1562)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(28), Batch(1), ExpectedCredits(1590)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(29), Batch(1), ExpectedCredits(1617)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(30), Batch(1), ExpectedCredits(1644)),
|
||||
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(932)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(987)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(1014)),
|
||||
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(1042)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(1069)),
|
||||
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(1096)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(1124)),
|
||||
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(1151)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(1179)),
|
||||
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(1206)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(1233)),
|
||||
(Some(CommonResolution::FourK), Seconds(16), Batch(1), ExpectedCredits(1261)),
|
||||
(Some(CommonResolution::FourK), Seconds(17), Batch(1), ExpectedCredits(1288)),
|
||||
(Some(CommonResolution::FourK), Seconds(18), Batch(1), ExpectedCredits(1316)),
|
||||
(Some(CommonResolution::FourK), Seconds(19), Batch(1), ExpectedCredits(1343)),
|
||||
(Some(CommonResolution::FourK), Seconds(20), Batch(1), ExpectedCredits(1370)),
|
||||
(Some(CommonResolution::FourK), Seconds(21), Batch(1), ExpectedCredits(1398)),
|
||||
(Some(CommonResolution::FourK), Seconds(22), Batch(1), ExpectedCredits(1425)),
|
||||
(Some(CommonResolution::FourK), Seconds(23), Batch(1), ExpectedCredits(1453)),
|
||||
(Some(CommonResolution::FourK), Seconds(24), Batch(1), ExpectedCredits(1480)),
|
||||
(Some(CommonResolution::FourK), Seconds(25), Batch(1), ExpectedCredits(1507)),
|
||||
(Some(CommonResolution::FourK), Seconds(26), Batch(1), ExpectedCredits(1535)),
|
||||
(Some(CommonResolution::FourK), Seconds(27), Batch(1), ExpectedCredits(1562)),
|
||||
(Some(CommonResolution::FourK), Seconds(28), Batch(1), ExpectedCredits(1590)),
|
||||
(Some(CommonResolution::FourK), Seconds(29), Batch(1), ExpectedCredits(1617)),
|
||||
(Some(CommonResolution::FourK), Seconds(30), Batch(1), ExpectedCredits(1644)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(4), Batch(1), ExpectedCredits(1309)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(1), ExpectedCredits(1386)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(1), ExpectedCredits(1425)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(8), Batch(1), ExpectedCredits(1463)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(1), ExpectedCredits(1502)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(10), Batch(1), ExpectedCredits(1540)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(1), ExpectedCredits(1579)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(12), Batch(1), ExpectedCredits(1617)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(1), ExpectedCredits(1656)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(14), Batch(1), ExpectedCredits(1694)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(1), ExpectedCredits(1733)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(16), Batch(1), ExpectedCredits(1771)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(17), Batch(1), ExpectedCredits(1810)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(18), Batch(1), ExpectedCredits(1848)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(19), Batch(1), ExpectedCredits(1887)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(20), Batch(1), ExpectedCredits(1925)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(21), Batch(1), ExpectedCredits(1964)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(22), Batch(1), ExpectedCredits(2002)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(23), Batch(1), ExpectedCredits(2041)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(24), Batch(1), ExpectedCredits(2079)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(25), Batch(1), ExpectedCredits(2118)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(26), Batch(1), ExpectedCredits(2156)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(27), Batch(1), ExpectedCredits(2195)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(28), Batch(1), ExpectedCredits(2233)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(29), Batch(1), ExpectedCredits(2272)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(30), Batch(1), ExpectedCredits(2310)),
|
||||
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(1309)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(1386)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(1425)),
|
||||
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(1463)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(1502)),
|
||||
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(1540)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(1579)),
|
||||
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(1617)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(1656)),
|
||||
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(1694)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(1733)),
|
||||
(Some(CommonResolution::FourK), Seconds(16), Batch(1), ExpectedCredits(1771)),
|
||||
(Some(CommonResolution::FourK), Seconds(17), Batch(1), ExpectedCredits(1810)),
|
||||
(Some(CommonResolution::FourK), Seconds(18), Batch(1), ExpectedCredits(1848)),
|
||||
(Some(CommonResolution::FourK), Seconds(19), Batch(1), ExpectedCredits(1887)),
|
||||
(Some(CommonResolution::FourK), Seconds(20), Batch(1), ExpectedCredits(1925)),
|
||||
(Some(CommonResolution::FourK), Seconds(21), Batch(1), ExpectedCredits(1964)),
|
||||
(Some(CommonResolution::FourK), Seconds(22), Batch(1), ExpectedCredits(2002)),
|
||||
(Some(CommonResolution::FourK), Seconds(23), Batch(1), ExpectedCredits(2041)),
|
||||
(Some(CommonResolution::FourK), Seconds(24), Batch(1), ExpectedCredits(2079)),
|
||||
(Some(CommonResolution::FourK), Seconds(25), Batch(1), ExpectedCredits(2118)),
|
||||
(Some(CommonResolution::FourK), Seconds(26), Batch(1), ExpectedCredits(2156)),
|
||||
(Some(CommonResolution::FourK), Seconds(27), Batch(1), ExpectedCredits(2195)),
|
||||
(Some(CommonResolution::FourK), Seconds(28), Batch(1), ExpectedCredits(2233)),
|
||||
(Some(CommonResolution::FourK), Seconds(29), Batch(1), ExpectedCredits(2272)),
|
||||
(Some(CommonResolution::FourK), Seconds(30), Batch(1), ExpectedCredits(2310)),
|
||||
];
|
||||
|
||||
for (resolution, seconds, batch, expected) in cases {
|
||||
@@ -405,26 +405,26 @@ mod seedance_2p5 {
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(8), ExpectedCredits(555)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(9), ExpectedCredits(555)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(10), ExpectedCredits(555)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(2), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(4), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(5), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(6), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(7), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(8), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(9), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(959)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(2), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(4), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(5), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(6), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(7), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(8), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(9), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(1348)),
|
||||
];
|
||||
|
||||
for (resolution, seconds, batch, expected) in cases {
|
||||
@@ -454,18 +454,18 @@ mod seedance_2p5 {
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(5), ExpectedCredits(650)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(8), ExpectedCredits(682)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(10), ExpectedCredits(713)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(2), ExpectedCredits(987)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(1014)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(4), ExpectedCredits(1069)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(1124)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(1179)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(1233)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(987)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(1014)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(1069)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(1124)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(1179)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(1233)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(2), ExpectedCredits(1386)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(1425)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(4), ExpectedCredits(1502)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(1579)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(1656)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(1733)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(1386)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(1425)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(1502)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(1579)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(1656)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(1733)),
|
||||
];
|
||||
|
||||
for (resolution, seconds, batch, expected) in cases {
|
||||
@@ -546,60 +546,60 @@ mod seedance_2p5_u {
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(28), Batch(1), ExpectedCredits(884)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(29), Batch(1), ExpectedCredits(916)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(30), Batch(1), ExpectedCredits(947)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(4), Batch(1), ExpectedCredits(201)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(1), ExpectedCredits(301)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(1), ExpectedCredits(351)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(8), Batch(1), ExpectedCredits(401)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(1), ExpectedCredits(451)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(10), Batch(1), ExpectedCredits(502)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(1), ExpectedCredits(552)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(12), Batch(1), ExpectedCredits(602)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(1), ExpectedCredits(652)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(14), Batch(1), ExpectedCredits(702)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(1), ExpectedCredits(752)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(16), Batch(1), ExpectedCredits(802)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(17), Batch(1), ExpectedCredits(852)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(18), Batch(1), ExpectedCredits(902)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(19), Batch(1), ExpectedCredits(952)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(20), Batch(1), ExpectedCredits(1003)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(21), Batch(1), ExpectedCredits(1053)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(22), Batch(1), ExpectedCredits(1103)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(23), Batch(1), ExpectedCredits(1153)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(24), Batch(1), ExpectedCredits(1203)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(25), Batch(1), ExpectedCredits(1253)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(26), Batch(1), ExpectedCredits(1303)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(27), Batch(1), ExpectedCredits(1353)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(28), Batch(1), ExpectedCredits(1403)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(29), Batch(1), ExpectedCredits(1454)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(30), Batch(1), ExpectedCredits(1504)),
|
||||
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(201)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(301)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(351)),
|
||||
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(401)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(451)),
|
||||
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(502)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(552)),
|
||||
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(602)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(652)),
|
||||
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(702)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(752)),
|
||||
(Some(CommonResolution::FourK), Seconds(16), Batch(1), ExpectedCredits(802)),
|
||||
(Some(CommonResolution::FourK), Seconds(17), Batch(1), ExpectedCredits(852)),
|
||||
(Some(CommonResolution::FourK), Seconds(18), Batch(1), ExpectedCredits(902)),
|
||||
(Some(CommonResolution::FourK), Seconds(19), Batch(1), ExpectedCredits(952)),
|
||||
(Some(CommonResolution::FourK), Seconds(20), Batch(1), ExpectedCredits(1003)),
|
||||
(Some(CommonResolution::FourK), Seconds(21), Batch(1), ExpectedCredits(1053)),
|
||||
(Some(CommonResolution::FourK), Seconds(22), Batch(1), ExpectedCredits(1103)),
|
||||
(Some(CommonResolution::FourK), Seconds(23), Batch(1), ExpectedCredits(1153)),
|
||||
(Some(CommonResolution::FourK), Seconds(24), Batch(1), ExpectedCredits(1203)),
|
||||
(Some(CommonResolution::FourK), Seconds(25), Batch(1), ExpectedCredits(1253)),
|
||||
(Some(CommonResolution::FourK), Seconds(26), Batch(1), ExpectedCredits(1303)),
|
||||
(Some(CommonResolution::FourK), Seconds(27), Batch(1), ExpectedCredits(1353)),
|
||||
(Some(CommonResolution::FourK), Seconds(28), Batch(1), ExpectedCredits(1403)),
|
||||
(Some(CommonResolution::FourK), Seconds(29), Batch(1), ExpectedCredits(1454)),
|
||||
(Some(CommonResolution::FourK), Seconds(30), Batch(1), ExpectedCredits(1504)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(4), Batch(1), ExpectedCredits(269)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(1), ExpectedCredits(404)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(1), ExpectedCredits(471)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(8), Batch(1), ExpectedCredits(538)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(1), ExpectedCredits(606)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(10), Batch(1), ExpectedCredits(673)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(1), ExpectedCredits(740)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(12), Batch(1), ExpectedCredits(807)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(1), ExpectedCredits(874)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(14), Batch(1), ExpectedCredits(942)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(1), ExpectedCredits(1009)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(16), Batch(1), ExpectedCredits(1076)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(17), Batch(1), ExpectedCredits(1143)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(18), Batch(1), ExpectedCredits(1211)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(19), Batch(1), ExpectedCredits(1278)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(20), Batch(1), ExpectedCredits(1345)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(21), Batch(1), ExpectedCredits(1412)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(22), Batch(1), ExpectedCredits(1480)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(23), Batch(1), ExpectedCredits(1547)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(24), Batch(1), ExpectedCredits(1614)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(25), Batch(1), ExpectedCredits(1681)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(26), Batch(1), ExpectedCredits(1748)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(27), Batch(1), ExpectedCredits(1816)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(28), Batch(1), ExpectedCredits(1883)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(29), Batch(1), ExpectedCredits(1950)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(30), Batch(1), ExpectedCredits(2017)),
|
||||
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(269)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(404)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(471)),
|
||||
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(538)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(606)),
|
||||
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(673)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(740)),
|
||||
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(807)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(874)),
|
||||
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(942)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(1009)),
|
||||
(Some(CommonResolution::FourK), Seconds(16), Batch(1), ExpectedCredits(1076)),
|
||||
(Some(CommonResolution::FourK), Seconds(17), Batch(1), ExpectedCredits(1143)),
|
||||
(Some(CommonResolution::FourK), Seconds(18), Batch(1), ExpectedCredits(1211)),
|
||||
(Some(CommonResolution::FourK), Seconds(19), Batch(1), ExpectedCredits(1278)),
|
||||
(Some(CommonResolution::FourK), Seconds(20), Batch(1), ExpectedCredits(1345)),
|
||||
(Some(CommonResolution::FourK), Seconds(21), Batch(1), ExpectedCredits(1412)),
|
||||
(Some(CommonResolution::FourK), Seconds(22), Batch(1), ExpectedCredits(1480)),
|
||||
(Some(CommonResolution::FourK), Seconds(23), Batch(1), ExpectedCredits(1547)),
|
||||
(Some(CommonResolution::FourK), Seconds(24), Batch(1), ExpectedCredits(1614)),
|
||||
(Some(CommonResolution::FourK), Seconds(25), Batch(1), ExpectedCredits(1681)),
|
||||
(Some(CommonResolution::FourK), Seconds(26), Batch(1), ExpectedCredits(1748)),
|
||||
(Some(CommonResolution::FourK), Seconds(27), Batch(1), ExpectedCredits(1816)),
|
||||
(Some(CommonResolution::FourK), Seconds(28), Batch(1), ExpectedCredits(1883)),
|
||||
(Some(CommonResolution::FourK), Seconds(29), Batch(1), ExpectedCredits(1950)),
|
||||
(Some(CommonResolution::FourK), Seconds(30), Batch(1), ExpectedCredits(2017)),
|
||||
];
|
||||
|
||||
for (resolution, seconds, batch, expected) in cases {
|
||||
@@ -638,26 +638,26 @@ mod seedance_2p5_u {
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(8), ExpectedCredits(158)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(9), ExpectedCredits(158)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(10), ExpectedCredits(158)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(2), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(4), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(5), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(6), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(7), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(8), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(9), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(251)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(2), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(4), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(5), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(6), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(7), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(8), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(9), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(337)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(337)),
|
||||
];
|
||||
|
||||
for (resolution, seconds, batch, expected) in cases {
|
||||
@@ -686,18 +686,18 @@ mod seedance_2p5_u {
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(5), ExpectedCredits(348)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(8), ExpectedCredits(411)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(10), ExpectedCredits(474)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(2), ExpectedCredits(301)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(351)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(4), ExpectedCredits(451)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(552)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(652)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(752)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(301)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(351)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(451)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(552)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(652)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(752)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(2), ExpectedCredits(404)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(471)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(4), ExpectedCredits(606)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(740)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(874)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(1009)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(404)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(471)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(606)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(740)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(874)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(1009)),
|
||||
];
|
||||
|
||||
for (resolution, seconds, batch, expected) in cases {
|
||||
@@ -769,60 +769,60 @@ mod seedance_2p5_u {
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(28), Batch(1), ExpectedCredits(1087)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(29), Batch(1), ExpectedCredits(1105)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(30), Batch(1), ExpectedCredits(1124)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(4), Batch(1), ExpectedCredits(1018)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(1), ExpectedCredits(1078)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(1), ExpectedCredits(1108)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(8), Batch(1), ExpectedCredits(1138)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(1), ExpectedCredits(1168)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(10), Batch(1), ExpectedCredits(1198)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(1), ExpectedCredits(1228)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(12), Batch(1), ExpectedCredits(1258)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(1), ExpectedCredits(1288)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(14), Batch(1), ExpectedCredits(1318)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(1), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(16), Batch(1), ExpectedCredits(1378)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(17), Batch(1), ExpectedCredits(1408)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(18), Batch(1), ExpectedCredits(1437)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(19), Batch(1), ExpectedCredits(1467)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(20), Batch(1), ExpectedCredits(1497)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(21), Batch(1), ExpectedCredits(1527)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(22), Batch(1), ExpectedCredits(1557)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(23), Batch(1), ExpectedCredits(1587)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(24), Batch(1), ExpectedCredits(1617)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(25), Batch(1), ExpectedCredits(1647)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(26), Batch(1), ExpectedCredits(1677)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(27), Batch(1), ExpectedCredits(1707)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(28), Batch(1), ExpectedCredits(1737)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(29), Batch(1), ExpectedCredits(1767)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(30), Batch(1), ExpectedCredits(1797)),
|
||||
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(1018)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(1078)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(1108)),
|
||||
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(1138)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(1168)),
|
||||
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(1198)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(1228)),
|
||||
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(1258)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(1288)),
|
||||
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(1318)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::FourK), Seconds(16), Batch(1), ExpectedCredits(1378)),
|
||||
(Some(CommonResolution::FourK), Seconds(17), Batch(1), ExpectedCredits(1408)),
|
||||
(Some(CommonResolution::FourK), Seconds(18), Batch(1), ExpectedCredits(1437)),
|
||||
(Some(CommonResolution::FourK), Seconds(19), Batch(1), ExpectedCredits(1467)),
|
||||
(Some(CommonResolution::FourK), Seconds(20), Batch(1), ExpectedCredits(1497)),
|
||||
(Some(CommonResolution::FourK), Seconds(21), Batch(1), ExpectedCredits(1527)),
|
||||
(Some(CommonResolution::FourK), Seconds(22), Batch(1), ExpectedCredits(1557)),
|
||||
(Some(CommonResolution::FourK), Seconds(23), Batch(1), ExpectedCredits(1587)),
|
||||
(Some(CommonResolution::FourK), Seconds(24), Batch(1), ExpectedCredits(1617)),
|
||||
(Some(CommonResolution::FourK), Seconds(25), Batch(1), ExpectedCredits(1647)),
|
||||
(Some(CommonResolution::FourK), Seconds(26), Batch(1), ExpectedCredits(1677)),
|
||||
(Some(CommonResolution::FourK), Seconds(27), Batch(1), ExpectedCredits(1707)),
|
||||
(Some(CommonResolution::FourK), Seconds(28), Batch(1), ExpectedCredits(1737)),
|
||||
(Some(CommonResolution::FourK), Seconds(29), Batch(1), ExpectedCredits(1767)),
|
||||
(Some(CommonResolution::FourK), Seconds(30), Batch(1), ExpectedCredits(1797)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(4), Batch(1), ExpectedCredits(1423)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(1), ExpectedCredits(1507)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(1), ExpectedCredits(1549)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(8), Batch(1), ExpectedCredits(1591)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(1), ExpectedCredits(1632)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(10), Batch(1), ExpectedCredits(1674)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(1), ExpectedCredits(1716)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(12), Batch(1), ExpectedCredits(1758)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(1), ExpectedCredits(1800)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(14), Batch(1), ExpectedCredits(1842)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(1), ExpectedCredits(1884)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(16), Batch(1), ExpectedCredits(1925)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(17), Batch(1), ExpectedCredits(1967)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(18), Batch(1), ExpectedCredits(2009)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(19), Batch(1), ExpectedCredits(2051)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(20), Batch(1), ExpectedCredits(2093)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(21), Batch(1), ExpectedCredits(2135)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(22), Batch(1), ExpectedCredits(2176)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(23), Batch(1), ExpectedCredits(2218)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(24), Batch(1), ExpectedCredits(2260)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(25), Batch(1), ExpectedCredits(2302)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(26), Batch(1), ExpectedCredits(2344)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(27), Batch(1), ExpectedCredits(2386)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(28), Batch(1), ExpectedCredits(2428)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(29), Batch(1), ExpectedCredits(2469)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(30), Batch(1), ExpectedCredits(2511)),
|
||||
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(1423)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(1507)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(1549)),
|
||||
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(1591)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(1632)),
|
||||
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(1674)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(1716)),
|
||||
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(1758)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(1800)),
|
||||
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(1842)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(1884)),
|
||||
(Some(CommonResolution::FourK), Seconds(16), Batch(1), ExpectedCredits(1925)),
|
||||
(Some(CommonResolution::FourK), Seconds(17), Batch(1), ExpectedCredits(1967)),
|
||||
(Some(CommonResolution::FourK), Seconds(18), Batch(1), ExpectedCredits(2009)),
|
||||
(Some(CommonResolution::FourK), Seconds(19), Batch(1), ExpectedCredits(2051)),
|
||||
(Some(CommonResolution::FourK), Seconds(20), Batch(1), ExpectedCredits(2093)),
|
||||
(Some(CommonResolution::FourK), Seconds(21), Batch(1), ExpectedCredits(2135)),
|
||||
(Some(CommonResolution::FourK), Seconds(22), Batch(1), ExpectedCredits(2176)),
|
||||
(Some(CommonResolution::FourK), Seconds(23), Batch(1), ExpectedCredits(2218)),
|
||||
(Some(CommonResolution::FourK), Seconds(24), Batch(1), ExpectedCredits(2260)),
|
||||
(Some(CommonResolution::FourK), Seconds(25), Batch(1), ExpectedCredits(2302)),
|
||||
(Some(CommonResolution::FourK), Seconds(26), Batch(1), ExpectedCredits(2344)),
|
||||
(Some(CommonResolution::FourK), Seconds(27), Batch(1), ExpectedCredits(2386)),
|
||||
(Some(CommonResolution::FourK), Seconds(28), Batch(1), ExpectedCredits(2428)),
|
||||
(Some(CommonResolution::FourK), Seconds(29), Batch(1), ExpectedCredits(2469)),
|
||||
(Some(CommonResolution::FourK), Seconds(30), Batch(1), ExpectedCredits(2511)),
|
||||
];
|
||||
|
||||
for (resolution, seconds, batch, expected) in cases {
|
||||
@@ -861,26 +861,26 @@ mod seedance_2p5_u {
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(8), ExpectedCredits(656)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(9), ExpectedCredits(656)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(10), ExpectedCredits(656)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(2), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(4), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(5), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(6), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(7), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(8), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(9), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(1048)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(2), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(4), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(5), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(6), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(7), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(8), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(9), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(1465)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(1465)),
|
||||
];
|
||||
|
||||
for (resolution, seconds, batch, expected) in cases {
|
||||
@@ -910,18 +910,18 @@ mod seedance_2p5_u {
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(5), ExpectedCredits(768)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(8), ExpectedCredits(806)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(10), ExpectedCredits(843)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(2), ExpectedCredits(1078)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(1108)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(4), ExpectedCredits(1168)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(1228)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(1288)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(1078)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(1108)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(1168)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(1228)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(1288)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(1348)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(2), ExpectedCredits(1507)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(1549)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(4), ExpectedCredits(1632)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(1716)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(1800)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(1884)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(1507)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(1549)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(1632)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(1716)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(1800)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(1884)),
|
||||
];
|
||||
|
||||
for (resolution, seconds, batch, expected) in cases {
|
||||
@@ -1059,7 +1059,7 @@ mod input_video_duration_billing {
|
||||
// 6.5s → 7 → 37 billed × 7.24279835 = 267.98 → 268
|
||||
// 10.5s → 11 → 41 billed × 7.24279835 = 296.95 → 297
|
||||
// 6.5s → 7 → 37 billed × 15.84362140 = 586.21 → 587 (720p)
|
||||
// 6.5s → 7 → 37 billed × 27.39973680 = 1013.79 → 1014 (1080p)
|
||||
// 6.5s → 7 → 37 billed × 38.49728574 = 1424.40 → 1425 (1080p)
|
||||
assert_real_input_videos_charge(
|
||||
&harness, CommonVideoModel::Seedance2p5, Some(CommonResolution::FourEightyP),
|
||||
Seconds(30), &[4_500], ExpectedCredits(254),
|
||||
@@ -1078,7 +1078,7 @@ mod input_video_duration_billing {
|
||||
).await;
|
||||
assert_real_input_videos_charge(
|
||||
&harness, CommonVideoModel::Seedance2p5, Some(CommonResolution::TenEightyP),
|
||||
Seconds(30), &[6_500], ExpectedCredits(1014),
|
||||
Seconds(30), &[6_500], ExpectedCredits(1425),
|
||||
).await;
|
||||
}
|
||||
|
||||
@@ -1122,12 +1122,12 @@ mod input_video_duration_billing {
|
||||
let harness = TestHarness::create().await;
|
||||
|
||||
// 30s output, ultra with-refs 480p 8.55967078 ¢/s / 720p 18.72427984 ¢/s
|
||||
// / 1080p 29.93674947 ¢/s:
|
||||
// / 1080p 41.84487580 ¢/s:
|
||||
// 2.5s → total 3 → clamps to 4 → 34 billed → 291.03 → 292
|
||||
// 10.5s → 11 → 41 billed → 350.95 → 351
|
||||
// 2.5s + 6.5s → 3+7=10 → 40 billed → 342.39 → 343 (total-based, no per-video clamp)
|
||||
// 6.5s → 7 → 37 billed × 18.72427984 = 692.80 → 693 (720p)
|
||||
// 6.5s → 7 → 37 billed × 29.93674947 = 1107.66 → 1108 (1080p)
|
||||
// 6.5s → 7 → 37 billed × 41.84487580 = 1548.26 → 1549 (1080p)
|
||||
assert_real_input_videos_charge(
|
||||
&harness, CommonVideoModel::Seedance2p5Ultra, Some(CommonResolution::FourEightyP),
|
||||
Seconds(30), &[2_500], ExpectedCredits(292),
|
||||
@@ -1146,7 +1146,7 @@ mod input_video_duration_billing {
|
||||
).await;
|
||||
assert_real_input_videos_charge(
|
||||
&harness, CommonVideoModel::Seedance2p5Ultra, Some(CommonResolution::TenEightyP),
|
||||
Seconds(30), &[6_500], ExpectedCredits(1108),
|
||||
Seconds(30), &[6_500], ExpectedCredits(1549),
|
||||
).await;
|
||||
}
|
||||
}
|
||||
@@ -1172,10 +1172,10 @@ mod premium {
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(10), Batch(1), ExpectedCredits(268), ExpectedCredits(316), CreditsDelta(48)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(1), ExpectedCredits(401), ExpectedCredits(474), CreditsDelta(73)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(30), Batch(1), ExpectedCredits(802), ExpectedCredits(947), CreditsDelta(145)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(230), ExpectedCredits(251), CreditsDelta(21)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(30), Batch(1), ExpectedCredits(1376), ExpectedCredits(1504), CreditsDelta(128)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(309), ExpectedCredits(337), CreditsDelta(28)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(30), Batch(1), ExpectedCredits(1849), ExpectedCredits(2017), CreditsDelta(168)),
|
||||
// 4K downgrades to 1080p on both models, so the premium matches 1080p.
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(230), ExpectedCredits(251), CreditsDelta(21)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(309), ExpectedCredits(337), CreditsDelta(28)),
|
||||
];
|
||||
|
||||
for (resolution, seconds, batch, base, variant, delta) in cases {
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<base href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="icon" type="image/svg+xml" href="/artcraft-icon.svg" />
|
||||
<link rel="stylesheet" href="/src/styles.css" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 116.34 97.5">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #4e7bfb;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<path class="cls-1" d="M104.28,49.49L81.55,0h-31.23l-3.17,4.76L14.75,53.63,0,75.85l21.55,21.55,63.79-36.94,16.99,37.04,14.01-21.74-12.06-26.27ZM32.89,65.66l32.42-48.87,10.91,23.77-43.32,25.09Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 483 B |
@@ -0,0 +1,9 @@
|
||||
export const AUTH_LABEL_CLASSES =
|
||||
"block font-mono text-[11px] font-semibold leading-4 uppercase tracking-[0.12em] text-white/70";
|
||||
|
||||
export const AUTH_INPUT_CLASSES =
|
||||
"h-10 w-full rounded-[3px] border border-white/15 bg-white/5 px-4 py-2 text-sm font-normal leading-5 text-white placeholder-white/20 outline-none transition-colors focus:border-white/40";
|
||||
|
||||
export const AUTH_PASSWORD_INPUT_CLASSES = `${AUTH_INPUT_CLASSES} pr-12`;
|
||||
|
||||
export const AUTH_FORM_PADDING = "px-6 py-10 sm:px-10 sm:py-12";
|
||||
@@ -1,11 +1,11 @@
|
||||
import { AUTH_FORM_PADDING } from "./auth-form-styles";
|
||||
import { ReactNode } from "react";
|
||||
import { Outlet, useNavigate, Navigate } from "react-router-dom";
|
||||
import { ArrowLeftIcon, LoaderCircleIcon } from "lucide-react";
|
||||
import { TruchetPattern } from "@storyteller/ui-vfx";
|
||||
import { Outlet, Navigate, Link } from "react-router-dom";
|
||||
import { LoaderCircleIcon } from "lucide-react";
|
||||
import { AuthShowcase } from "./auth-showcase";
|
||||
import { AuthPageFrame } from "./auth-page-frame";
|
||||
import { useMediaQuery } from "../ui/use-media-query";
|
||||
import { useSession } from "../../lib/session";
|
||||
import { Reveal, RevealGroup } from "../motion/reveal";
|
||||
|
||||
/**
|
||||
* Persistent shell for the auth pages. Rendered as a layout route so the
|
||||
@@ -18,52 +18,21 @@ export const AuthLayout = () => {
|
||||
// Only mount the showcase on wide screens (matches the `lg` breakpoint) so
|
||||
// mobile never downloads the demo videos.
|
||||
const showShowcase = useMediaQuery("(min-width: 1024px)");
|
||||
const navigate = useNavigate();
|
||||
const { loggedIn, authChecked } = useSession();
|
||||
|
||||
const handleBack = () => {
|
||||
navigate("/");
|
||||
};
|
||||
|
||||
// Already signed in? Never show the auth form — go straight home.
|
||||
if (loggedIn) {
|
||||
return <Navigate to="/" replace />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-ui-background p-4 text-white">
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-0 z-0"
|
||||
style={{
|
||||
maskImage:
|
||||
"radial-gradient(ellipse 70% 60% at 50% 40%, black 25%, transparent 80%)",
|
||||
WebkitMaskImage:
|
||||
"radial-gradient(ellipse 70% 60% at 50% 40%, black 25%, transparent 80%)",
|
||||
}}
|
||||
>
|
||||
<TruchetPattern
|
||||
intensity={0.5}
|
||||
className="absolute inset-0 h-full w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 flex w-full max-w-5xl overflow-hidden border border-white/15 bg-[#101014] lg:min-h-[640px]">
|
||||
<AuthPageFrame wide>
|
||||
{/* Form pane — only the inner content swaps per route (via Outlet), so
|
||||
this pane and the showcase beside it stay mounted across the
|
||||
login/signup toggle. */}
|
||||
<div className="relative flex w-full flex-col lg:w-1/2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleBack}
|
||||
aria-label="Go back"
|
||||
className="absolute left-5 top-5 z-20 flex h-9 w-9 items-center justify-center text-white/60 transition-colors hover:bg-white/10 hover:text-white"
|
||||
>
|
||||
<ArrowLeftIcon />
|
||||
</button>
|
||||
|
||||
<div className="flex flex-1 flex-col justify-center px-8 py-10 sm:px-10">
|
||||
<div className="mx-auto w-full max-w-sm">
|
||||
<div className={`flex flex-1 flex-col justify-center ${AUTH_FORM_PADDING}`}>
|
||||
<div className="w-full">
|
||||
{authChecked ? (
|
||||
<Outlet />
|
||||
) : (
|
||||
@@ -76,44 +45,39 @@ export const AuthLayout = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-8 pb-8 text-center text-xs text-white/20">
|
||||
© {new Date().getFullYear()} ArtCraft. All rights reserved.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Showcase pane (desktop only) */}
|
||||
{showShowcase && (
|
||||
<div className="relative lg:w-1/2">
|
||||
<div className="relative border-l border-white/15 lg:w-1/2">
|
||||
<AuthShowcase />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</AuthPageFrame>
|
||||
);
|
||||
};
|
||||
|
||||
interface AuthHeaderProps {
|
||||
title: string;
|
||||
title: ReactNode;
|
||||
subtitle: string;
|
||||
}
|
||||
|
||||
export const AuthHeader = ({ title, subtitle }: AuthHeaderProps) => (
|
||||
<RevealGroup inView={false} stagger={0.08} className="mb-8 text-center">
|
||||
<Reveal y={10}>
|
||||
<img
|
||||
src="/images/artcraft-icon.png"
|
||||
alt="ArtCraft"
|
||||
className="mx-auto mb-6 h-12 w-auto select-none pointer-events-none"
|
||||
draggable={false}
|
||||
/>
|
||||
</Reveal>
|
||||
<Reveal as="h1" y={10} className="mb-2 text-2xl font-semibold">
|
||||
<div className="mb-8 text-left">
|
||||
<Link
|
||||
to="/"
|
||||
aria-label="ArtCraft home"
|
||||
className="mb-8 inline-flex rounded-sm transition-opacity hover:opacity-80 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-white/60"
|
||||
>
|
||||
<img src="/artcraft-icon.svg" alt="ArtCraft" className="h-8 w-8" />
|
||||
</Link>
|
||||
<h1 className="mb-3 text-balance font-display text-3xl leading-[1.05] tracking-tight sm:text-4xl">
|
||||
{title}
|
||||
</Reveal>
|
||||
<Reveal as="p" y={10} className="text-sm text-white/60">
|
||||
</h1>
|
||||
<p className="text-sm leading-relaxed text-white/60">
|
||||
{subtitle}
|
||||
</Reveal>
|
||||
</RevealGroup>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const AuthFooter = ({ children }: { children: ReactNode }) => (
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { type ReactNode } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { FrameCornerMarks } from "../ui/frame-corner-marks";
|
||||
|
||||
/** A contained section frame shared by the auth pages. */
|
||||
export function AuthPageFrame({ children, wide = false }: {
|
||||
children: ReactNode;
|
||||
wide?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="relative isolate flex min-h-dvh items-center justify-center overflow-hidden bg-ui-background p-4 text-white sm:py-10">
|
||||
<div className={twMerge("relative w-full", wide ? "max-w-lg lg:max-w-5xl" : "max-w-lg")}>
|
||||
<div className="relative border border-white/20 bg-ui-controls">
|
||||
<div className={twMerge("flex", wide ? "lg:min-h-[560px]" : "flex-col")}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
<FrameCornerMarks />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,7 +3,7 @@ const FEATURED_VIDEO_URL =
|
||||
|
||||
export const AuthShowcase = () => {
|
||||
return (
|
||||
<div className="absolute inset-2 overflow-hidden bg-black">
|
||||
<div className="absolute inset-0 overflow-hidden bg-ui-controls">
|
||||
{/* Muted background video (no controls) cropped to cover the pane */}
|
||||
<video
|
||||
src={FEATURED_VIDEO_URL}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AUTH_LABEL_CLASSES, AUTH_INPUT_CLASSES, AUTH_PASSWORD_INPUT_CLASSES } from "./auth-form-styles";
|
||||
import { EyeIcon, EyeOffIcon, LoaderCircleIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
@@ -14,7 +15,6 @@ import {
|
||||
import { refreshSession } from "../../lib/session";
|
||||
import { hasActiveSubscription } from "../../lib/billing";
|
||||
import { GoogleLoginButton } from "./GoogleLoginButton";
|
||||
import { Reveal, RevealGroup } from "../motion/reveal";
|
||||
|
||||
interface SignupFormProps {
|
||||
onSuccess: (isNewUser?: boolean) => void;
|
||||
@@ -98,7 +98,7 @@ export const SignupForm = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`space-y-4 ${className}`}>
|
||||
<div className={className}>
|
||||
<form
|
||||
className="space-y-4"
|
||||
onSubmit={(e) => {
|
||||
@@ -111,19 +111,11 @@ export const SignupForm = ({
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Fields cascade in as their own beat, leading with the email field on
|
||||
an almost-zero delay so the primary input lands immediately — the
|
||||
entrance never gates typing (inputs stay focusable mid-cascade).
|
||||
Mirrors the login page. */}
|
||||
<RevealGroup
|
||||
inView={false}
|
||||
delayChildren={0.04}
|
||||
stagger={0.08}
|
||||
<div
|
||||
className="space-y-4"
|
||||
>
|
||||
<Reveal className="space-y-2">
|
||||
<label className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/70">
|
||||
<div className="space-y-2">
|
||||
<label className={AUTH_LABEL_CLASSES}>
|
||||
Email
|
||||
</label>
|
||||
<Input
|
||||
@@ -132,13 +124,13 @@ export const SignupForm = ({
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="you@example.com"
|
||||
autoFocus={autoFocus}
|
||||
inputClassName="w-full bg-ui-controls border border-white/15 focus:border-white/40 rounded-[3px] px-4 py-3 text-white placeholder-white/20 outline-none transition-colors"
|
||||
inputClassName={AUTH_INPUT_CLASSES}
|
||||
/>
|
||||
</Reveal>
|
||||
</div>
|
||||
|
||||
<Reveal className="space-y-2">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<label className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/70">
|
||||
<label className={AUTH_LABEL_CLASSES}>
|
||||
Password
|
||||
</label>
|
||||
</div>
|
||||
@@ -148,7 +140,7 @@ export const SignupForm = ({
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Min. 8 characters"
|
||||
inputClassName="w-full bg-ui-controls border border-white/15 focus:border-white/40 rounded-[3px] px-4 py-3 text-white placeholder-white/20 outline-none transition-colors pr-12"
|
||||
inputClassName={AUTH_PASSWORD_INPUT_CLASSES}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@@ -159,9 +151,9 @@ export const SignupForm = ({
|
||||
<DynamicIcon icon={showPassword ? EyeOffIcon : EyeIcon} />
|
||||
</button>
|
||||
</div>
|
||||
</Reveal>
|
||||
</div>
|
||||
|
||||
<Reveal className="pt-2">
|
||||
<div className="pt-2">
|
||||
<Button
|
||||
className="w-full justify-center h-10"
|
||||
type="submit"
|
||||
@@ -173,36 +165,25 @@ export const SignupForm = ({
|
||||
"Create account"
|
||||
)}
|
||||
</Button>
|
||||
</Reveal>
|
||||
</RevealGroup>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{/* Secondary sign-up options continue the same cascade just after the
|
||||
fields (delay continues from the form group's three children above:
|
||||
0.04 + 3 × 0.08). */}
|
||||
<RevealGroup
|
||||
inView={false}
|
||||
delayChildren={0.28}
|
||||
stagger={0.08}
|
||||
className="space-y-4"
|
||||
>
|
||||
<Reveal className="relative flex items-center justify-center py-2">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-white/10" />
|
||||
</div>
|
||||
<span className="relative bg-[#101014] px-4 font-mono text-[11px] uppercase tracking-[0.12em] text-white/40">
|
||||
<div>
|
||||
<div className="my-6 flex items-center gap-4 before:h-px before:flex-1 before:bg-white/15 after:h-px after:flex-1 after:bg-white/15">
|
||||
<span className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/40">
|
||||
or
|
||||
</span>
|
||||
</Reveal>
|
||||
</div>
|
||||
|
||||
<Reveal>
|
||||
<div>
|
||||
<GoogleLoginButton
|
||||
mode="signup"
|
||||
onSuccess={handleGoogleSuccess}
|
||||
onError={handleGoogleError}
|
||||
/>
|
||||
</Reveal>
|
||||
</RevealGroup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { type ReactNode } from "react";
|
||||
import { FrameCornerMarks } from "../ui/frame-corner-marks";
|
||||
|
||||
interface CreateEmptyStateProps {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
children?: ReactNode;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
/** A quiet editorial section with a fine border and corner registration marks. */
|
||||
export function CreateEmptyState({ title, subtitle, children, compact = false }: CreateEmptyStateProps) {
|
||||
return (
|
||||
<section className={`create-empty-section relative w-full max-w-3xl px-6 text-left sm:px-10 ${compact ? "py-8" : "py-10 sm:py-14"}`}>
|
||||
<h1 className={`text-balance font-display leading-[1.05] tracking-[-0.035em] text-ui-ink ${compact ? "text-2xl" : "text-3xl md:text-5xl"}`}>
|
||||
{title}
|
||||
</h1>
|
||||
<p className="mt-4 max-w-xl text-pretty text-sm leading-relaxed text-white/60 sm:text-base">
|
||||
{subtitle}
|
||||
</p>
|
||||
{children && <div className="mt-6">{children}</div>}
|
||||
<FrameCornerMarks />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
import { CreateEmptyState } from "./CreateEmptyState";
|
||||
import { type ReactNode, useState } from "react";
|
||||
import { LoaderCircleIcon } from "lucide-react";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import { type PopoverItem } from "@storyteller/ui-popover";
|
||||
import { TabSelector } from "@storyteller/ui-tab-selector";
|
||||
import { TruchetPattern } from "@storyteller/ui-vfx";
|
||||
import Seo from "../../components/seo";
|
||||
import { useIsMobile } from "../ui/use-mobile";
|
||||
import {
|
||||
@@ -20,7 +19,7 @@ interface CreateMediaPageShellProps {
|
||||
// SEO
|
||||
title: string;
|
||||
description: string;
|
||||
// Auth state — `authChecked` gates the initial spinner so we don't flash
|
||||
// Auth state - `authChecked` gates the initial spinner so we don't flash
|
||||
// logged-out chrome while the session resolves. Pages stay viewable for
|
||||
// logged-out users; the signup CTA modal is triggered at generate time.
|
||||
authChecked: boolean;
|
||||
@@ -41,8 +40,6 @@ interface CreateMediaPageShellProps {
|
||||
// Model selector
|
||||
modelItems: PopoverItem[];
|
||||
onModelChange: (item: PopoverItem) => void;
|
||||
// Glow orb overrides (optional - defaults provided)
|
||||
glowOrbs?: ReactNode;
|
||||
// Children slots
|
||||
gridContent: ReactNode;
|
||||
promptBox: ReactNode;
|
||||
@@ -69,7 +66,6 @@ export function CreateMediaPageShell({
|
||||
emptyStateSubtitle,
|
||||
emptyStateCta,
|
||||
bottomOffset,
|
||||
glowOrbs,
|
||||
gridContent,
|
||||
promptBox,
|
||||
promptForm,
|
||||
@@ -103,9 +99,9 @@ export function CreateMediaPageShell({
|
||||
<Seo title={title} description={description} />
|
||||
|
||||
{/* Flex (not absolute) keeps the toggle cluster from overlapping
|
||||
the tabs — the tabs stay centered via the equal flex-1 sides and
|
||||
the tabs - the tabs stay centered via the equal flex-1 sides and
|
||||
just shift left if the cluster outgrows its half. */}
|
||||
<div className="flex items-center border-b border-ui-panel-border px-3 py-2">
|
||||
<div className="flex flex-wrap items-center gap-y-2 border-b border-ui-panel-border bg-ui-panel px-3 py-2">
|
||||
<div className="flex-1" />
|
||||
<TabSelector
|
||||
tabs={MOBILE_TABS}
|
||||
@@ -132,13 +128,8 @@ export function CreateMediaPageShell({
|
||||
<div className="px-3">{gridContent}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-full flex-col items-center justify-center px-6 text-center">
|
||||
<span className="text-lg font-semibold text-white">
|
||||
{emptyStateTitle}
|
||||
</span>
|
||||
<span className="pt-1 text-sm text-white/60">
|
||||
Your generations will appear here.
|
||||
</span>
|
||||
<div className="flex h-full items-center justify-center overflow-y-auto px-6 py-8">
|
||||
<CreateEmptyState compact title={emptyStateTitle} subtitle="Your generations will appear here." />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -150,51 +141,16 @@ export function CreateMediaPageShell({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full bg-ui-background text-white">
|
||||
<div className="relative isolate flex h-full w-full bg-ui-background text-white">
|
||||
<Seo title={title} description={description} />
|
||||
|
||||
{/* Decorative background, empty state only. Not rendered on mobile
|
||||
devices: the truchet uses mask-image, a full-screen GPU layer that
|
||||
iOS Safari must re-rasterize when an overlay (mobile menu, modal)
|
||||
composites over it, causing multi-second hangs. (This is distinct
|
||||
from backdrop-filter.) We gate on the actual device
|
||||
(react-device-detect's user-agent check) rather than a viewport
|
||||
breakpoint so the layers never mount on a real phone/tablet, and a
|
||||
narrow desktop window still gets the decoration. */}
|
||||
{!hasContent && !isMobile && (
|
||||
<>
|
||||
{glowOrbs}
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none fixed inset-0 z-0"
|
||||
style={{
|
||||
maskImage:
|
||||
"radial-gradient(ellipse 70% 60% at 50% 50%, black 20%, transparent 80%)",
|
||||
WebkitMaskImage:
|
||||
"radial-gradient(ellipse 70% 60% at 50% 50%, black 20%, transparent 80%)",
|
||||
}}
|
||||
>
|
||||
<TruchetPattern
|
||||
intensity={0.5}
|
||||
className="absolute inset-0 h-full w-full"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="relative z-[1] h-full w-full">
|
||||
<div className="flex h-full w-full flex-col">
|
||||
{!hasContent && (
|
||||
<div className="flex flex-1 items-center justify-center">
|
||||
<div className="animate-fade-in-up relative z-20 mb-32 flex flex-col items-center justify-center text-center">
|
||||
<h1 className="text-5xl font-semibold text-white md:text-7xl">
|
||||
{emptyStateTitle}
|
||||
</h1>
|
||||
<span className="pt-2 text-lg text-white/80 md:text-xl">
|
||||
{emptyStateSubtitle}
|
||||
</span>
|
||||
{emptyStateCta && <div className="pt-6">{emptyStateCta}</div>}
|
||||
</div>
|
||||
<div className="flex flex-1 items-center justify-center px-6" style={{ paddingBottom: bottomOffset }}>
|
||||
<CreateEmptyState title={emptyStateTitle} subtitle={emptyStateSubtitle}>
|
||||
{emptyStateCta}
|
||||
</CreateEmptyState>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -45,11 +45,11 @@ import {
|
||||
|
||||
function InfoRow({ label, value }: { label: string; value: React.ReactNode }) {
|
||||
return (
|
||||
<div className="flex items-center justify-between px-4 py-3 border-b border-white/10 last:border-0">
|
||||
<span className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/60">
|
||||
<div className="flex items-start justify-between gap-4 px-4 py-3 border-b border-white/10 last:border-0">
|
||||
<span className="shrink-0 pt-0.5 font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/60">
|
||||
{label}
|
||||
</span>
|
||||
<span className="text-sm text-white font-medium flex items-center gap-2">
|
||||
<span className="min-w-0 text-right text-sm text-white font-medium flex items-center justify-end gap-2">
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
@@ -426,7 +426,14 @@ export function LightboxDetails({
|
||||
{createdAt && (
|
||||
<InfoRow
|
||||
label="Created"
|
||||
value={dayjs(createdAt).format("MMMM D, YYYY h:mm:ss A")}
|
||||
value={
|
||||
<time dateTime={createdAt} className="flex flex-col items-end gap-0.5 tabular-nums">
|
||||
<span>{dayjs(createdAt).format("MMM D, YYYY")}</span>
|
||||
<span className="text-xs text-white/60 whitespace-nowrap">
|
||||
{dayjs(createdAt).format("h:mm:ss A")}
|
||||
</span>
|
||||
</time>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -357,7 +357,7 @@ export function Lightbox({
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
className="h-[90vh] sm:h-[680px] w-full sm:w-[1100px] max-w-[95vw] max-h-[90vh] p-0 border-white/15"
|
||||
className="h-[90dvh] sm:h-[92dvh] w-full sm:w-[1440px] max-w-[96vw] max-h-[92dvh] p-0 border-white/15"
|
||||
backdropClassName="!bg-black/80"
|
||||
showClose={false}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { type ReactNode } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { NEUTRAL_BUTTON_HOVER_CLASSES } from "@storyteller/ui-button";
|
||||
import { ChevronRightIcon } from "lucide-react";
|
||||
|
||||
interface MobileFieldButtonProps {
|
||||
@@ -27,7 +28,8 @@ export function MobileFieldButton({
|
||||
disabled={disabled}
|
||||
className={twMerge(
|
||||
"flex w-full items-center gap-3 border border-ui-panel-border bg-ui-controls px-3.5 py-3 text-left transition-colors",
|
||||
"hover:bg-ui-controls/80 active:scale-[0.99] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
NEUTRAL_BUTTON_HOVER_CLASSES,
|
||||
"disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-ui-controls disabled:hover:border-ui-panel-border",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMemo } from "react";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import { SidebarActiveIndicator } from "./sidebar-active-indicator";
|
||||
import {
|
||||
BoxIcon,
|
||||
CircleHelpIcon,
|
||||
@@ -161,7 +161,6 @@ function NavMenuItem({
|
||||
pathname: string;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
const reduceMotion = useReducedMotion();
|
||||
const active = !item.external && isActive(pathname, item.href);
|
||||
const inner = (
|
||||
<>
|
||||
@@ -187,21 +186,7 @@ function NavMenuItem({
|
||||
);
|
||||
return (
|
||||
<SidebarMenuItem>
|
||||
{/* Brand accent bar that glides between nav rows as the active route
|
||||
changes (shared-element layout animation via `layoutId`). One is
|
||||
mounted at a time, so framer-motion tweens it from the old row to the
|
||||
new one. */}
|
||||
{active && (
|
||||
<motion.span
|
||||
layoutId="sidebar-active-indicator"
|
||||
className="pointer-events-none absolute inset-y-1.5 left-0 z-10 w-0.5 bg-white"
|
||||
transition={
|
||||
reduceMotion
|
||||
? { duration: 0 }
|
||||
: { type: "spring", stiffness: 520, damping: 40, mass: 0.7 }
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{active && <SidebarActiveIndicator />}
|
||||
<SidebarMenuButton asChild isActive={active} tooltip={item.label}>
|
||||
{item.external ? (
|
||||
<a
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { SidebarActiveIndicator } from "./sidebar-active-indicator";
|
||||
import { useMemo, useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
@@ -90,6 +91,7 @@ export function LibraryFoldersNav({
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
{onUnsorted && <SidebarActiveIndicator />}
|
||||
<SidebarMenuButton asChild isActive={onUnsorted} tooltip="Library">
|
||||
<Link to="/library" onClick={onNavClick}>
|
||||
<Grid3x3Icon />
|
||||
@@ -98,6 +100,7 @@ export function LibraryFoldersNav({
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
{onFolderless && <SidebarActiveIndicator />}
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
isActive={onFolderless}
|
||||
@@ -110,6 +113,7 @@ export function LibraryFoldersNav({
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
{onFolders && !activeFolderId && <SidebarActiveIndicator />}
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
isActive={onFolders && !activeFolderId}
|
||||
@@ -147,8 +151,9 @@ export function LibraryFoldersNav({
|
||||
<ul className="flex w-full min-w-0 flex-col gap-0.5">
|
||||
{rootFolders.map((folder) => (
|
||||
<SidebarMenuItem key={folder.id}>
|
||||
{onFolders && activeRootId === folder.id && <SidebarActiveIndicator />}
|
||||
<SidebarMenuButton
|
||||
isActive={activeRootId === folder.id}
|
||||
isActive={onFolders && activeRootId === folder.id}
|
||||
tooltip={folder.name}
|
||||
data-folder-id={folder.id}
|
||||
onClick={() => goToFolder(folder.id)}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { SidebarActiveIndicator } from "./sidebar-active-indicator";
|
||||
import { useMemo, useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
@@ -48,6 +49,7 @@ export function LibraryTagsNav({
|
||||
return (
|
||||
<>
|
||||
<SidebarMenuItem>
|
||||
{pathname === "/library/tags" && <SidebarActiveIndicator />}
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
isActive={pathname === "/library/tags"}
|
||||
@@ -85,6 +87,7 @@ export function LibraryTagsNav({
|
||||
<ul className="flex w-full min-w-0 flex-col gap-0.5">
|
||||
{topTags.map((tag) => (
|
||||
<SidebarMenuItem key={tag.token}>
|
||||
{pathname === `/library/${tag.token}` && <SidebarActiveIndicator />}
|
||||
<SidebarMenuButton
|
||||
isActive={pathname === `/library/${tag.token}`}
|
||||
tooltip={tag.value}
|
||||
|
||||
@@ -24,16 +24,19 @@ export function MobileBottomNav() {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
return (
|
||||
<nav className="flex shrink-0 items-stretch border-t border-ui-panel-border bg-ui-panel pb-[env(safe-area-inset-bottom)]">
|
||||
<nav aria-label="Main navigation" className="flex shrink-0 items-stretch border-t border-ui-panel-border bg-ui-panel pb-[env(safe-area-inset-bottom)]">
|
||||
{ITEMS.map((item) => {
|
||||
const active = isActive(pathname, item.href);
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
to={item.href}
|
||||
aria-current={active ? "page" : undefined}
|
||||
className={twMerge(
|
||||
"flex flex-1 flex-col items-center justify-center gap-1 py-2 font-mono text-[10px] font-semibold uppercase tracking-[0.12em] transition-colors",
|
||||
active ? "text-white" : "text-base-fg/55 hover:text-base-fg/80",
|
||||
"flex min-h-14 flex-1 flex-col items-center justify-center gap-1 border-t-2 py-2 font-mono text-[10px] font-semibold uppercase tracking-[0.12em] transition-colors",
|
||||
active
|
||||
? "border-primary bg-primary/10 text-ui-accent-ink"
|
||||
: "border-transparent text-base-fg/70 hover:text-base-fg",
|
||||
)}
|
||||
>
|
||||
<DynamicIcon icon={item.icon} className="h-5 w-5" />
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
|
||||
export function SidebarActiveIndicator() {
|
||||
const reduceMotion = useReducedMotion();
|
||||
|
||||
return (
|
||||
<motion.span
|
||||
aria-hidden="true"
|
||||
layoutId="sidebar-active-indicator"
|
||||
className="pointer-events-none absolute inset-y-1.5 left-0 z-10 w-0.5 bg-white"
|
||||
transition={
|
||||
reduceMotion
|
||||
? { duration: 0 }
|
||||
: { type: "spring", stiffness: 520, damping: 40, mass: 0.7 }
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useLocation, Link } from "react-router-dom";
|
||||
import { Fragment } from "react";
|
||||
import { ChevronRightIcon } from "lucide-react";
|
||||
|
||||
type Crumb = { label: string; href?: string };
|
||||
|
||||
@@ -77,26 +78,27 @@ export function Breadcrumbs() {
|
||||
if (crumbs.length === 0) return null;
|
||||
|
||||
return (
|
||||
<nav className="hidden md:flex items-center gap-1.5 font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/45 min-w-0">
|
||||
<nav aria-label="Breadcrumb" className="hidden md:flex items-center gap-1.5 hud-label text-base-fg/70 min-w-0">
|
||||
{crumbs.map((crumb, i) => {
|
||||
const isLast = i === crumbs.length - 1;
|
||||
return (
|
||||
<Fragment key={`${crumb.label}-${i}`}>
|
||||
{i > 0 && (
|
||||
<span className="text-white/25" aria-hidden>
|
||||
›
|
||||
</span>
|
||||
<ChevronRightIcon
|
||||
aria-hidden="true"
|
||||
className="h-3 w-3 shrink-0 text-base-fg/50"
|
||||
/>
|
||||
)}
|
||||
{crumb.href && !isLast ? (
|
||||
<Link
|
||||
to={crumb.href}
|
||||
className="truncate hover:text-white/70 transition-colors"
|
||||
className="truncate px-1.5 py-1 hover:bg-ui-ink hover:text-ui-panel transition-colors"
|
||||
>
|
||||
{crumb.label}
|
||||
</Link>
|
||||
) : (
|
||||
<span
|
||||
className={isLast ? "text-white/85 truncate" : "truncate"}
|
||||
className={isLast ? "bg-ui-ink px-1.5 py-1 text-ui-panel truncate" : "truncate"}
|
||||
aria-current={isLast ? "page" : undefined}
|
||||
>
|
||||
{crumb.label}
|
||||
|
||||
@@ -24,7 +24,7 @@ export function TopBar() {
|
||||
const showSelectToggle = showViewToggle;
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-20 flex items-center gap-3 border-b border-white/15 bg-ui-background px-3 pb-4 pt-3 sm:pt-6">
|
||||
<header className="sticky top-0 z-20 flex items-center gap-3 border-b border-ui-border bg-ui-panel px-3 py-3 sm:px-5">
|
||||
{/* Left: sidebar trigger (mobile only) + logo (when sidebar closed) + breadcrumbs */}
|
||||
<div className="flex items-center gap-2 min-w-0 shrink-0">
|
||||
<SidebarTrigger className="md:hidden" />
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
const CORNERS = ["top-0 left-0", "top-0 right-0", "bottom-0 left-0", "bottom-0 right-0"];
|
||||
|
||||
/** Decorative registration marks for a positioned panel. */
|
||||
export function FrameCornerMarks() {
|
||||
return CORNERS.map((corner) => (
|
||||
<span key={corner} aria-hidden="true" className={`frame-corner-mark ${corner}`} />
|
||||
));
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CreateEmptyState } from "../../components/generation-gallery/CreateEmptyState";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { LoaderCircleIcon, SparklesIcon } from "lucide-react";
|
||||
import { LoaderCircleIcon } from "lucide-react";
|
||||
import {
|
||||
PromptBoxVFX,
|
||||
VFXResultCard,
|
||||
@@ -267,7 +268,7 @@ export default function CreateVFX() {
|
||||
className="relative z-10 flex h-full items-center justify-center px-3 sm:px-6"
|
||||
style={{ paddingBottom: Math.max(promptBoxHeight + 32, 240) }}
|
||||
>
|
||||
<EmptyState
|
||||
<CreateEmptyState
|
||||
title="No background changes yet"
|
||||
subtitle="Upload a source video and a reference image, then optionally add a prompt."
|
||||
/>
|
||||
@@ -330,18 +331,3 @@ export default function CreateVFX() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface EmptyStateProps {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
}
|
||||
|
||||
const EmptyState = ({ title, subtitle }: EmptyStateProps) => (
|
||||
<div className="flex max-w-md flex-col items-center gap-4 text-center">
|
||||
<div className="flex h-12 w-12 items-center justify-center border border-white/15 bg-white/5">
|
||||
<SparklesIcon className="text-2xl text-white/40" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-white">{title}</h3>
|
||||
<p className="text-sm text-white/60 max-w-xs">{subtitle}</p>
|
||||
</div>
|
||||
);
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AUTH_LABEL_CLASSES, AUTH_INPUT_CLASSES, AUTH_FORM_PADDING } from "../../components/auth/auth-form-styles";
|
||||
import { ArrowLeftIcon, LoaderCircleIcon, MailIcon } from "lucide-react";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
@@ -5,6 +6,8 @@ import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { PasswordResetApi } from "@storyteller/api";
|
||||
|
||||
import { AuthHeader } from "../../components/auth/auth-layout";
|
||||
import { AuthPageFrame } from "../../components/auth/auth-page-frame";
|
||||
import Seo from "../../components/seo";
|
||||
|
||||
const ForgotPassword = () => {
|
||||
@@ -41,28 +44,17 @@ const ForgotPassword = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative min-h-screen bg-ui-background text-white overflow-hidden flex flex-col">
|
||||
<AuthPageFrame>
|
||||
<Seo
|
||||
title="Reset Password - ArtCraft"
|
||||
description="Reset your ArtCraft password."
|
||||
/>
|
||||
|
||||
<main className="relative z-10 flex-1 flex items-center justify-center p-4">
|
||||
<div className="w-full max-w-md bg-[#101014] border border-white/15 p-6 py-8">
|
||||
<main className={AUTH_FORM_PADDING}>
|
||||
<div className="w-full">
|
||||
{!submitted ? (
|
||||
<>
|
||||
<div className="text-center mb-8">
|
||||
<img
|
||||
src="/images/artcraft-icon.png"
|
||||
alt="ArtCraft"
|
||||
className="mx-auto mb-6 h-12 w-auto select-none pointer-events-none"
|
||||
draggable={false}
|
||||
/>
|
||||
<h1 className="text-2xl font-semibold mb-2">Reset Password</h1>
|
||||
<p className="text-white/60 text-sm">
|
||||
Enter your email to receive reset instructions
|
||||
</p>
|
||||
</div>
|
||||
<AuthHeader title={<>Reset your <span className="font-serif-italic">password.</span></>} subtitle="Enter your email or username to receive a reset code." />
|
||||
|
||||
<form
|
||||
className="space-y-4"
|
||||
@@ -78,7 +70,7 @@ const ForgotPassword = () => {
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/60 ml-1">
|
||||
<label className={AUTH_LABEL_CLASSES}>
|
||||
Email or Username
|
||||
</label>
|
||||
<Input
|
||||
@@ -87,7 +79,7 @@ const ForgotPassword = () => {
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="you@example.com"
|
||||
inputClassName="w-full bg-ui-controls border border-white/15 focus:border-white/40 px-4 py-3 text-white placeholder-white/20 outline-none transition-colors"
|
||||
inputClassName={AUTH_INPUT_CLASSES}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -134,18 +126,14 @@ const ForgotPassword = () => {
|
||||
<div className="mt-8 text-center text-sm">
|
||||
<Link
|
||||
to="/login"
|
||||
className="text-white/40 hover:text-white transition-colors flex items-center justify-center gap-2"
|
||||
className="text-white/60 hover:text-white transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
<ArrowLeftIcon /> Back to Log in
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div className="relative z-10 py-6 text-center text-white/20 text-xs">
|
||||
© {new Date().getFullYear()} ArtCraft. All rights reserved.
|
||||
</div>
|
||||
</div>
|
||||
</AuthPageFrame>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AUTH_LABEL_CLASSES, AUTH_INPUT_CLASSES, AUTH_PASSWORD_INPUT_CLASSES, AUTH_FORM_PADDING } from "../../components/auth/auth-form-styles";
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
CircleCheckIcon,
|
||||
@@ -12,6 +13,8 @@ import { useState } from "react";
|
||||
import { Link, useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { PasswordResetApi, BillingApi } from "@storyteller/api";
|
||||
|
||||
import { AuthHeader } from "../../components/auth/auth-layout";
|
||||
import { AuthPageFrame } from "../../components/auth/auth-page-frame";
|
||||
import Seo from "../../components/seo";
|
||||
|
||||
const VerifyReset = () => {
|
||||
@@ -102,30 +105,17 @@ const VerifyReset = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative min-h-screen bg-ui-background text-white overflow-hidden flex flex-col">
|
||||
<AuthPageFrame>
|
||||
<Seo
|
||||
title="Verify Password Reset - ArtCraft"
|
||||
description="Enter your verification code and new password."
|
||||
/>
|
||||
|
||||
<main className="relative z-10 flex-1 flex items-center justify-center p-4">
|
||||
<div className="w-full max-w-md bg-[#101014] border border-white/15 p-6 py-8">
|
||||
<main className={AUTH_FORM_PADDING}>
|
||||
<div className="w-full">
|
||||
{!success ? (
|
||||
<>
|
||||
<div className="text-center mb-8">
|
||||
<img
|
||||
src="/images/artcraft-icon.png"
|
||||
alt="ArtCraft"
|
||||
className="mx-auto mb-6 h-12 w-auto select-none pointer-events-none"
|
||||
draggable={false}
|
||||
/>
|
||||
<h1 className="text-2xl font-semibold mb-2">
|
||||
Password Reset Verification
|
||||
</h1>
|
||||
<p className="text-white/60 text-sm">
|
||||
Enter the code sent to your email address.
|
||||
</p>
|
||||
</div>
|
||||
<AuthHeader title={<>Set a new <span className="font-serif-italic">password.</span></>} subtitle="Enter your reset code and choose a new password." />
|
||||
|
||||
<form
|
||||
className="space-y-4"
|
||||
@@ -142,7 +132,7 @@ const VerifyReset = () => {
|
||||
|
||||
{/* Verification Code */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/60 ml-1">
|
||||
<label className={AUTH_LABEL_CLASSES}>
|
||||
Verification Code
|
||||
</label>
|
||||
<Input
|
||||
@@ -152,10 +142,10 @@ const VerifyReset = () => {
|
||||
onChange={(e) => setVerificationCode(e.target.value)}
|
||||
placeholder="Enter verification code"
|
||||
isError={!!fieldErrors.verificationCode}
|
||||
inputClassName="w-full bg-ui-controls border border-white/15 focus:border-white/40 px-4 py-3 text-white placeholder-white/20 outline-none transition-colors"
|
||||
inputClassName={AUTH_INPUT_CLASSES}
|
||||
/>
|
||||
{fieldErrors.verificationCode && (
|
||||
<p className="text-red-400 text-xs ml-1">
|
||||
<p className="text-red-400 text-xs">
|
||||
{fieldErrors.verificationCode}
|
||||
</p>
|
||||
)}
|
||||
@@ -163,7 +153,7 @@ const VerifyReset = () => {
|
||||
|
||||
{/* New Password */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/60 ml-1">
|
||||
<label className={AUTH_LABEL_CLASSES}>
|
||||
New Password
|
||||
</label>
|
||||
<div className="relative">
|
||||
@@ -174,7 +164,7 @@ const VerifyReset = () => {
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
placeholder="Enter new password"
|
||||
isError={!!fieldErrors.newPassword}
|
||||
inputClassName="w-full bg-ui-controls border border-white/15 focus:border-white/40 px-4 py-3 text-white placeholder-white/20 outline-none transition-colors pr-12"
|
||||
inputClassName={AUTH_PASSWORD_INPUT_CLASSES}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@@ -187,7 +177,7 @@ const VerifyReset = () => {
|
||||
</button>
|
||||
</div>
|
||||
{fieldErrors.newPassword && (
|
||||
<p className="text-red-400 text-xs ml-1">
|
||||
<p className="text-red-400 text-xs">
|
||||
{fieldErrors.newPassword}
|
||||
</p>
|
||||
)}
|
||||
@@ -195,7 +185,7 @@ const VerifyReset = () => {
|
||||
|
||||
{/* Verify New Password */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/60 ml-1">
|
||||
<label className={AUTH_LABEL_CLASSES}>
|
||||
Verify New Password
|
||||
</label>
|
||||
<div className="relative">
|
||||
@@ -206,7 +196,7 @@ const VerifyReset = () => {
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
placeholder="Enter new password again"
|
||||
isError={!!fieldErrors.confirmPassword}
|
||||
inputClassName="w-full bg-ui-controls border border-white/15 focus:border-white/40 px-4 py-3 text-white placeholder-white/20 outline-none transition-colors pr-12"
|
||||
inputClassName={AUTH_PASSWORD_INPUT_CLASSES}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@@ -221,7 +211,7 @@ const VerifyReset = () => {
|
||||
</button>
|
||||
</div>
|
||||
{fieldErrors.confirmPassword && (
|
||||
<p className="text-red-400 text-xs ml-1">
|
||||
<p className="text-red-400 text-xs">
|
||||
{fieldErrors.confirmPassword}
|
||||
</p>
|
||||
)}
|
||||
@@ -246,7 +236,7 @@ const VerifyReset = () => {
|
||||
<div className="mt-8 text-center text-sm">
|
||||
<Link
|
||||
to="/forgot-password"
|
||||
className="text-white/40 hover:text-white transition-colors flex items-center justify-center gap-2"
|
||||
className="text-white/60 hover:text-white transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
<ArrowLeftIcon /> Request a new code
|
||||
</Link>
|
||||
@@ -277,11 +267,7 @@ const VerifyReset = () => {
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div className="relative z-10 py-6 text-center text-white/20 text-xs">
|
||||
© {new Date().getFullYear()} ArtCraft. All rights reserved.
|
||||
</div>
|
||||
</div>
|
||||
</AuthPageFrame>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ type AppCard = {
|
||||
href: string;
|
||||
icon: LucideIcon;
|
||||
iconTile: string;
|
||||
hoverBorder: string;
|
||||
hoverStyle: string;
|
||||
badge?: string;
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ const APPS: AppCard[] = [
|
||||
href: "/create-image",
|
||||
icon: ImageIcon,
|
||||
iconTile: "border-blue-400/40 bg-blue-500/20 text-blue-300",
|
||||
hoverBorder: "hover:border-blue-400/60",
|
||||
hoverStyle: "hover:border-blue-400/60 hover:bg-blue-500/10",
|
||||
},
|
||||
{
|
||||
label: "Video",
|
||||
@@ -41,7 +41,7 @@ const APPS: AppCard[] = [
|
||||
href: "/create-video",
|
||||
icon: VideoIcon,
|
||||
iconTile: "border-purple-400/40 bg-purple-500/20 text-purple-300",
|
||||
hoverBorder: "hover:border-purple-400/60",
|
||||
hoverStyle: "hover:border-purple-400/60 hover:bg-purple-500/10",
|
||||
},
|
||||
{
|
||||
label: "Audio",
|
||||
@@ -49,7 +49,7 @@ const APPS: AppCard[] = [
|
||||
href: "/create-audio",
|
||||
icon: MusicIcon,
|
||||
iconTile: "border-pink-400/40 bg-pink-500/20 text-pink-300",
|
||||
hoverBorder: "hover:border-pink-400/60",
|
||||
hoverStyle: "hover:border-pink-400/60 hover:bg-pink-500/10",
|
||||
},
|
||||
{
|
||||
label: "3D Object",
|
||||
@@ -57,7 +57,7 @@ const APPS: AppCard[] = [
|
||||
href: "/create-object",
|
||||
icon: BoxIcon,
|
||||
iconTile: "border-cyan-400/40 bg-cyan-500/20 text-cyan-300",
|
||||
hoverBorder: "hover:border-cyan-400/60",
|
||||
hoverStyle: "hover:border-cyan-400/60 hover:bg-cyan-500/10",
|
||||
},
|
||||
{
|
||||
label: "3D World",
|
||||
@@ -65,7 +65,7 @@ const APPS: AppCard[] = [
|
||||
href: "/create-world",
|
||||
icon: GlobeIcon,
|
||||
iconTile: "border-teal-400/40 bg-teal-500/20 text-teal-300",
|
||||
hoverBorder: "hover:border-teal-400/60",
|
||||
hoverStyle: "hover:border-teal-400/60 hover:bg-teal-500/10",
|
||||
},
|
||||
{
|
||||
label: "Edit 3D",
|
||||
@@ -73,7 +73,7 @@ const APPS: AppCard[] = [
|
||||
href: "/edit-3d",
|
||||
icon: BoxIcon,
|
||||
iconTile: "border-amber-400/40 bg-amber-500/20 text-amber-300",
|
||||
hoverBorder: "hover:border-amber-400/60",
|
||||
hoverStyle: "hover:border-amber-400/60 hover:bg-amber-500/10",
|
||||
},
|
||||
{
|
||||
label: "Background Change",
|
||||
@@ -81,7 +81,7 @@ const APPS: AppCard[] = [
|
||||
href: "/background-change",
|
||||
icon: WandSparklesIcon,
|
||||
iconTile: "border-emerald-400/40 bg-emerald-500/20 text-emerald-300",
|
||||
hoverBorder: "hover:border-emerald-400/60",
|
||||
hoverStyle: "hover:border-emerald-400/60 hover:bg-emerald-500/10",
|
||||
},
|
||||
{
|
||||
label: "Edit Video",
|
||||
@@ -89,7 +89,7 @@ const APPS: AppCard[] = [
|
||||
href: "/video-editor",
|
||||
icon: FilmIcon,
|
||||
iconTile: "border-rose-400/40 bg-rose-500/20 text-rose-300",
|
||||
hoverBorder: "hover:border-rose-400/60",
|
||||
hoverStyle: "hover:border-rose-400/60 hover:bg-rose-500/10",
|
||||
badge: "BETA",
|
||||
},
|
||||
{
|
||||
@@ -98,7 +98,7 @@ const APPS: AppCard[] = [
|
||||
href: "/moodboard",
|
||||
icon: GroupIcon,
|
||||
iconTile: "border-indigo-400/40 bg-indigo-500/20 text-indigo-300",
|
||||
hoverBorder: "hover:border-indigo-400/60",
|
||||
hoverStyle: "hover:border-indigo-400/60 hover:bg-indigo-500/10",
|
||||
badge: "BETA",
|
||||
},
|
||||
{
|
||||
@@ -107,33 +107,38 @@ const APPS: AppCard[] = [
|
||||
href: "/frame-extractor",
|
||||
icon: ImagesIcon,
|
||||
iconTile: "border-orange-400/40 bg-orange-500/20 text-orange-300",
|
||||
hoverBorder: "hover:border-orange-400/60",
|
||||
hoverStyle: "hover:border-orange-400/60 hover:bg-orange-500/10",
|
||||
badge: "NEW",
|
||||
},
|
||||
];
|
||||
|
||||
export function Home() {
|
||||
return (
|
||||
<div className="min-h-full px-6 sm:px-10 py-10 sm:py-16 max-w-6xl mx-auto w-full">
|
||||
<div className="min-h-full px-5 py-8 sm:px-8 sm:py-12 max-w-6xl mx-auto w-full">
|
||||
<Seo
|
||||
title="ArtCraft - Create AI Images and Video"
|
||||
description="Generate AI images and video with ArtCraft."
|
||||
/>
|
||||
<p className="hud-label mb-4 text-ui-accent-ink">Your creative workspace</p>
|
||||
<Reveal
|
||||
as="h1"
|
||||
inView={false}
|
||||
y={20}
|
||||
className="text-center font-display text-4xl sm:text-6xl mx-auto font-semibold tracking-tight"
|
||||
className="max-w-3xl font-display text-3xl leading-tight sm:text-5xl tracking-tight"
|
||||
>
|
||||
What will you <span className="text-primary">craft</span> today?
|
||||
What will you <span className="text-ui-accent-ink">craft</span> today?
|
||||
</Reveal>
|
||||
|
||||
<section className="py-12">
|
||||
<p className="mt-4 max-w-xl text-sm leading-relaxed text-base-fg/70 sm:text-base">
|
||||
Start with an idea. Choose a tool to bring it to life.
|
||||
</p>
|
||||
|
||||
<section className="mt-8 border-t border-ui-border pt-6 sm:mt-10">
|
||||
<Reveal
|
||||
as="h2"
|
||||
inView={false}
|
||||
delay={0.08}
|
||||
className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/60 mb-4"
|
||||
className="hud-label text-base-fg/70 mb-4"
|
||||
>
|
||||
Create
|
||||
</Reveal>
|
||||
@@ -141,33 +146,29 @@ export function Home() {
|
||||
inView={false}
|
||||
delayChildren={0.12}
|
||||
stagger={0.06}
|
||||
className="grid gap-3 auto-rows-fr sm:grid-cols-2 lg:grid-cols-3"
|
||||
className="grid gap-3 auto-rows-fr md:grid-cols-2 xl:grid-cols-3"
|
||||
>
|
||||
{APPS.map((app) => (
|
||||
<Reveal key={app.href} y={20}>
|
||||
<Link
|
||||
to={app.href}
|
||||
className={`group relative flex h-full border rounded-[3px] border-white/10 bg-white/5 p-5 transition-colors duration-150 ${app.hoverBorder}`}
|
||||
className={`group relative flex h-full rounded-[3px] border border-ui-border bg-white/5 p-5 transition-colors duration-150 focus-visible:border-primary ${app.hoverStyle}`}
|
||||
>
|
||||
<div className="relative flex w-full items-start gap-4">
|
||||
<div
|
||||
className={`flex h-11 w-11 shrink-0 items-center justify-center border ${app.iconTile}`}
|
||||
className={`flex h-10 w-10 shrink-0 items-center justify-center border ${app.iconTile}`}
|
||||
>
|
||||
<DynamicIcon icon={app.icon} className="text-base" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<h3 className=" text-sm font-bold uppercase tracking-[0.08em] text-white truncate">
|
||||
<div className="flex flex-wrap items-center gap-2 min-w-0">
|
||||
<h3 className="text-base font-bold uppercase tracking-tight text-ui-ink">
|
||||
{app.label}
|
||||
</h3>
|
||||
{app.badge && (
|
||||
<span
|
||||
className={`shrink-0 border px-1.5 py-0.5 font-mono text-[9px] font-semibold uppercase tracking-[0.12em] leading-none ${
|
||||
app.badge === "NEW"
|
||||
? "border-purple-400/40 text-purple-300"
|
||||
: "border-amber-400/40 text-amber-300"
|
||||
}`}
|
||||
className="shrink-0 border border-primary/30 px-1.5 py-1 font-mono text-[9px] font-medium tracking-wider leading-none text-ui-accent-ink"
|
||||
>
|
||||
{app.badge}
|
||||
</span>
|
||||
@@ -177,7 +178,7 @@ export function Home() {
|
||||
<ArrowRightIcon className="text-xs" />
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 text-sm text-white/55 leading-snug">
|
||||
<p className="mt-2 text-sm text-base-fg/70 leading-relaxed">
|
||||
{app.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1375,7 +1375,7 @@ export default function Library() {
|
||||
</span>
|
||||
))}
|
||||
<span className="text-white/30">/</span>
|
||||
<h1 className="text-lg sm:text-xl font-medium text-white truncate max-w-[16rem]">
|
||||
<h1 className="font-sans text-sm font-semibold tracking-normal [font-stretch:normal] text-white truncate max-w-[16rem]">
|
||||
{activeFolder?.name}
|
||||
</h1>
|
||||
<button
|
||||
@@ -1405,11 +1405,11 @@ export default function Library() {
|
||||
Tags
|
||||
</button>
|
||||
<span className="text-white/30">/</span>
|
||||
<h1 className="text-lg sm:text-xl font-medium text-white truncate max-w-[16rem]">
|
||||
<h1 className="font-sans text-sm font-semibold tracking-normal [font-stretch:normal] text-white truncate max-w-[16rem]">
|
||||
{activeTag?.value ?? "Tag"}
|
||||
</h1>
|
||||
{activeTag && (
|
||||
<span className="text-white/40 text-sm pt-1 ps-1.5">
|
||||
<span className="text-white/40 text-xs ps-1.5">
|
||||
{activeTag.useCount} file
|
||||
{activeTag.useCount === 1 ? "" : "s"}
|
||||
</span>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AUTH_LABEL_CLASSES, AUTH_INPUT_CLASSES, AUTH_PASSWORD_INPUT_CLASSES } from "../../components/auth/auth-form-styles";
|
||||
import { EyeIcon, EyeOffIcon, LoaderCircleIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
GoogleLoginButton,
|
||||
} from "../../components/auth";
|
||||
import Seo from "../../components/seo";
|
||||
import { Reveal, RevealGroup } from "../../components/motion/reveal";
|
||||
import { refreshSession } from "../../lib/session";
|
||||
import { hasActiveSubscription } from "../../lib/billing";
|
||||
|
||||
@@ -70,7 +70,7 @@ const Login = () => {
|
||||
title="Login - ArtCraft"
|
||||
description="Login to your ArtCraft account."
|
||||
/>
|
||||
<AuthHeader title="Welcome Back" subtitle="Log in to your account" />
|
||||
<AuthHeader title={<>Welcome <span className="font-serif-italic">back.</span></>} subtitle="Log in to your creative workspace." />
|
||||
|
||||
<form
|
||||
className="space-y-4"
|
||||
@@ -84,20 +84,11 @@ const Login = () => {
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* The form fields cascade in as their own beat, distinct from the
|
||||
shell/header settling. We lead with the email field on an almost-zero
|
||||
delay so the primary input lands immediately — the entrance never
|
||||
gates typing: inputs stay focusable and accept keystrokes while the
|
||||
password/button below are still cascading in. */}
|
||||
<RevealGroup
|
||||
inView={false}
|
||||
delayChildren={0.04}
|
||||
stagger={0.08}
|
||||
<div
|
||||
className="space-y-4"
|
||||
>
|
||||
<Reveal className="space-y-2">
|
||||
<label className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/70 ml-1">
|
||||
<div className="space-y-2">
|
||||
<label className={AUTH_LABEL_CLASSES}>
|
||||
Email or Username
|
||||
</label>
|
||||
<Input
|
||||
@@ -105,12 +96,12 @@ const Login = () => {
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="you@example.com or username"
|
||||
inputClassName="w-full bg-ui-controls border border-white/15 focus:border-white/40 px-4 py-3 text-white placeholder-white/20 outline-none transition-colors"
|
||||
inputClassName={AUTH_INPUT_CLASSES}
|
||||
/>
|
||||
</Reveal>
|
||||
<Reveal className="space-y-2">
|
||||
<div className="flex justify-between items-center ml-1">
|
||||
<label className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/70">
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<label className={AUTH_LABEL_CLASSES}>
|
||||
Password
|
||||
</label>
|
||||
<Link
|
||||
@@ -126,7 +117,7 @@ const Login = () => {
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Min. 8 characters"
|
||||
inputClassName="w-full bg-ui-controls border border-white/15 focus:border-white/40 px-4 py-3 text-white placeholder-white/20 outline-none transition-colors pr-12"
|
||||
inputClassName={AUTH_PASSWORD_INPUT_CLASSES}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@@ -137,9 +128,9 @@ const Login = () => {
|
||||
<DynamicIcon icon={showPassword ? EyeOffIcon : EyeIcon} />
|
||||
</button>
|
||||
</div>
|
||||
</Reveal>
|
||||
</div>
|
||||
|
||||
<Reveal className="pt-2">
|
||||
<div className="pt-2">
|
||||
<Button
|
||||
className="w-full justify-center h-10"
|
||||
type="submit"
|
||||
@@ -151,32 +142,25 @@ const Login = () => {
|
||||
"Log in"
|
||||
)}
|
||||
</Button>
|
||||
</Reveal>
|
||||
</RevealGroup>
|
||||
</form>
|
||||
|
||||
{/* Secondary sign-in options pick up the same cascade just after the
|
||||
fields (delay continues from the form group's three children above:
|
||||
0.04 + 3 × 0.08). */}
|
||||
<RevealGroup inView={false} delayChildren={0.28} stagger={0.08}>
|
||||
<Reveal className="relative my-6 flex items-center justify-center">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-white/15" />
|
||||
</div>
|
||||
<span className="relative bg-[#101014] px-4 font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/40">
|
||||
</div>
|
||||
</form>
|
||||
<div>
|
||||
<div className="my-6 flex items-center gap-4 before:h-px before:flex-1 before:bg-white/15 after:h-px after:flex-1 after:bg-white/15">
|
||||
<span className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/40">
|
||||
or
|
||||
</span>
|
||||
</Reveal>
|
||||
</div>
|
||||
|
||||
<Reveal>
|
||||
<div>
|
||||
<GoogleLoginButton
|
||||
mode="login"
|
||||
onSuccess={handleGoogleSuccess}
|
||||
onError={handleGoogleError}
|
||||
/>
|
||||
</Reveal>
|
||||
</div>
|
||||
|
||||
<Reveal>
|
||||
<div>
|
||||
<AuthFooter>
|
||||
Don't have an account?{" "}
|
||||
<Link
|
||||
@@ -186,8 +170,8 @@ const Login = () => {
|
||||
Sign up
|
||||
</Link>
|
||||
</AuthFooter>
|
||||
</Reveal>
|
||||
</RevealGroup>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ export function SceneSplashModal({
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={close}
|
||||
className="w-full max-w-3xl overflow-hidden border border-white/15 bg-ui-modal p-0"
|
||||
className="w-full max-w-3xl overflow-hidden border border-white/15 bg-ui-controls p-0"
|
||||
childPadding={false}
|
||||
backdropClassName="!bg-black/80"
|
||||
closeOnOutsideClick
|
||||
|
||||
@@ -1,86 +1,12 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { CoinsIcon } from "lucide-react";
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
import { Link, useSearchParams } from "react-router-dom";
|
||||
import { CoinsIcon, TagIcon } from "lucide-react";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { BillingApi, UsersApi } from "@storyteller/api";
|
||||
import { PricingTable, PROMO_PCT } from "@storyteller/ui-pricing-table";
|
||||
import "@storyteller/ui-pricing-table/styles.css";
|
||||
import Seo from "../../components/seo";
|
||||
import {
|
||||
PricingTable,
|
||||
PricingPromoBanner,
|
||||
} from "@storyteller/ui-pricing-table";
|
||||
import { CreditsModal } from "../../components/credits-modal";
|
||||
import { Reveal } from "../../components/motion/reveal";
|
||||
|
||||
const SeedanceBanner = () => (
|
||||
<div className="flex flex-col gap-5">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="inline-flex items-center font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white bg-white/10 border border-white/30 px-3 py-1">
|
||||
Early access
|
||||
</span>
|
||||
<span className="inline-flex items-center font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/65 bg-white/5 border border-white/15 px-3 py-1">
|
||||
Available today in ArtCraft
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1 className="text-3xl md:text-4xl lg:text-5xl tracking-[-0.035em] font-medium leading-[1.05] mb-3">
|
||||
Seedance 2.0 is <span className="font-serif-italic">here</span>
|
||||
</h1>
|
||||
<p className="text-white/55 text-base md:text-lg leading-relaxed">
|
||||
Generate jaw-dropping AI videos with Seedance 2.0 before it's available
|
||||
anywhere else. Every paid plan includes video credits, so you can start
|
||||
creating right now.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative w-full overflow-hidden bg-[#080808] border border-white/15">
|
||||
<div style={{ paddingTop: "56.25%" }} className="relative">
|
||||
<iframe
|
||||
src="https://player.vimeo.com/video/1169289718?autoplay=1&muted=1&loop=1&background=0&byline=0&portrait=0&title=0"
|
||||
className="absolute inset-0 w-full h-full"
|
||||
allow="autoplay; fullscreen; picture-in-picture"
|
||||
allowFullScreen
|
||||
title="Seedance in ArtCraft"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="bg-[#101014] border border-white/30 p-4">
|
||||
<div className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white mb-1">
|
||||
Seedance video credits
|
||||
</div>
|
||||
<div className="text-white/55 text-sm leading-snug">
|
||||
Included with every paid ArtCraft plan
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-[#101014] border border-white/15 p-4">
|
||||
<div className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/70 mb-1">
|
||||
First in the world
|
||||
</div>
|
||||
<div className="text-white/55 text-sm leading-snug">
|
||||
Seedance launches in ArtCraft ahead of anywhere else
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Compact "top up" CTA shown above the plan table for users who already have a
|
||||
// plan, so returning subscribers can buy one-time credit packs without scrolling.
|
||||
const BuyMoreCreditsCta = ({ onClick }: { onClick: () => void }) => (
|
||||
<div className="mb-8 flex flex-col items-center text-center">
|
||||
<p className="text-base text-white/65">Need more credits?</p>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="mt-3 gap-2 border border-white/15 bg-transparent hover:bg-white/10 px-5 py-2 h-11 text-white"
|
||||
onClick={onClick}
|
||||
>
|
||||
<CoinsIcon className="text-[13px]" />
|
||||
Buy more credits
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
const Pricing = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
@@ -118,85 +44,153 @@ const Pricing = () => {
|
||||
title="Pricing - ArtCraft"
|
||||
description="Simple, transparent pricing for ArtCraft. Start for free and scale as you grow."
|
||||
/>
|
||||
|
||||
<main className="relative z-10 px-4 sm:px-8 pt-10">
|
||||
{isSeedanceRef ? (
|
||||
<div className="max-w-6xl mx-auto grid grid-cols-1 lg:grid-cols-[5fr_7fr] gap-8 xl:gap-12 items-start">
|
||||
<SeedanceBanner />
|
||||
<div className="w-full">
|
||||
{hasPlan && (
|
||||
<BuyMoreCreditsCta onClick={() => setCreditsModalOpen(true)} />
|
||||
)}
|
||||
<PricingTable
|
||||
showHeader={false}
|
||||
unifiedTheme
|
||||
compact
|
||||
showSeedanceFeatures
|
||||
showEnterprise
|
||||
/>
|
||||
<main className="pb-16">
|
||||
{!isSeedanceRef && (
|
||||
<div className="border-t border-white/15 bg-primary text-white">
|
||||
<div className="mx-auto flex max-w-[1280px] flex-wrap items-center justify-between gap-x-6 gap-y-1 px-6 py-2.5 md:px-10">
|
||||
<p className="pricing-hud flex items-center gap-2 font-bold">
|
||||
<TagIcon aria-hidden className="h-3.5 w-3.5" />
|
||||
Limited-time offer
|
||||
</p>
|
||||
<p className="text-sm">
|
||||
Save {PROMO_PCT}% on every plan, monthly or yearly — lock in the
|
||||
lowest price today.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<PricingPromoBanner className="mb-10 sm:mb-12" />
|
||||
<Reveal
|
||||
as="div"
|
||||
inView={false}
|
||||
y={20}
|
||||
className="text-center mb-14"
|
||||
>
|
||||
<h1 className="text-4xl sm:text-5xl md:text-6xl tracking-[-0.035em] font-medium leading-[1.02] mb-5">
|
||||
Invest in <span className="font-serif-italic">yourself</span>.
|
||||
</h1>
|
||||
<p className="max-w-xl mx-auto text-base sm:text-lg text-white/55 leading-relaxed">
|
||||
Get a ton of generations and invest in a tool you'll always own.
|
||||
Your subscription helps keep ArtCraft free and open for
|
||||
everyone.
|
||||
</p>
|
||||
</Reveal>
|
||||
{hasPlan && (
|
||||
<BuyMoreCreditsCta onClick={() => setCreditsModalOpen(true)} />
|
||||
)}
|
||||
<PricingTable
|
||||
showHeader={false}
|
||||
unifiedTheme
|
||||
showSeedanceFeatures
|
||||
showEnterprise
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
|
||||
{isLoggedIn && !hasPlan && (
|
||||
<div className="relative z-10 flex flex-col items-center px-4 pb-4 sm:px-8 pt-6">
|
||||
<div className="inline-flex items-center gap-2 text-white/40">
|
||||
<div className="h-px w-8 bg-white/20" />
|
||||
<span className="text-sm">Or</span>
|
||||
<div className="h-px w-8 bg-white/20" />
|
||||
<PricingSection>
|
||||
<SectionLabel
|
||||
index="01"
|
||||
label={isSeedanceRef ? "Early access" : "Pricing"}
|
||||
annotation={
|
||||
isSeedanceRef
|
||||
? "Available today in ArtCraft"
|
||||
: "Free & open source · Subscriptions optional†"
|
||||
}
|
||||
/>
|
||||
<div className="px-6 py-14 md:px-10 md:py-20">
|
||||
<h1 className="max-w-3xl font-display text-4xl font-medium leading-[1.02] tracking-[-0.035em] sm:text-5xl md:text-6xl">
|
||||
{isSeedanceRef ? (
|
||||
<>
|
||||
Seedance 2.0 is{" "}
|
||||
<span className="font-serif-italic">here</span>.
|
||||
</>
|
||||
) : (
|
||||
"Pick a plan"
|
||||
)}
|
||||
</h1>
|
||||
<p className="mt-5 max-w-xl text-lg leading-relaxed text-white/55">
|
||||
{isSeedanceRef
|
||||
? "Generate jaw-dropping AI videos with Seedance 2.0 before it's available anywhere else. Every paid plan includes video credits, so you can start creating right now."
|
||||
: "Get a ton of generations and invest in a tool you'll always own. Your subscription helps keep ArtCraft free and open for everyone."}
|
||||
</p>
|
||||
{hasPlan && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="mt-6 gap-2 rounded-none text-[11px]"
|
||||
onClick={() => setCreditsModalOpen(true)}
|
||||
>
|
||||
<CoinsIcon aria-hidden className="h-4 w-4" />
|
||||
Buy more credits
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-3 text-base text-white/65">
|
||||
Purchase one-time credit packs
|
||||
</p>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="mt-4 gap-2 border border-white/15 bg-transparent hover:bg-white/10 px-5 py-2 h-11 text-white"
|
||||
onClick={() => setCreditsModalOpen(true)}
|
||||
>
|
||||
<CoinsIcon className="text-[13px]" />
|
||||
Buy Credits
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Footnote */}
|
||||
<div className="relative z-10 max-w-3xl mx-auto px-4 sm:px-8 pb-16 text-center mt-6">
|
||||
<p className="text-sm text-white/45 leading-relaxed">
|
||||
† ArtCraft can be used without paying for a subscription. You
|
||||
can bring your own compute and third party subscriptions. We hope
|
||||
you'll subscribe, though, as that helps accelerate our development.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</PricingSection>
|
||||
{isSeedanceRef && (
|
||||
<PricingSection>
|
||||
<div className="grid lg:grid-cols-[3fr_2fr]">
|
||||
<figure className="min-w-0">
|
||||
<figcaption className="pricing-hud flex justify-between border-b border-white/15 px-6 py-2.5 text-white/55 md:px-8">
|
||||
<span>Seedance in ArtCraft</span>
|
||||
<span>Reel</span>
|
||||
</figcaption>
|
||||
<div className="relative aspect-video overflow-hidden bg-black">
|
||||
<iframe
|
||||
src="https://player.vimeo.com/video/1169289718?autoplay=1&muted=1&loop=1&background=0&byline=0&portrait=0&title=0"
|
||||
className="absolute inset-0 h-full w-full"
|
||||
allow="autoplay; fullscreen; picture-in-picture"
|
||||
allowFullScreen
|
||||
title="Seedance in ArtCraft"
|
||||
/>
|
||||
</div>
|
||||
</figure>
|
||||
<div className="grid border-t border-white/15 sm:grid-cols-2 lg:grid-cols-1 lg:border-l lg:border-t-0">
|
||||
<div className="p-6 md:p-8">
|
||||
<p className="pricing-hud text-primary">
|
||||
Seedance video credits
|
||||
</p>
|
||||
<p className="mt-3 leading-relaxed text-white/55">
|
||||
Included with every paid ArtCraft plan.
|
||||
</p>
|
||||
</div>
|
||||
<div className="border-t border-white/15 p-6 sm:border-l sm:border-t-0 md:p-8 lg:border-l-0 lg:border-t">
|
||||
<p className="pricing-hud text-white/55">
|
||||
First in the world
|
||||
</p>
|
||||
<p className="mt-3 leading-relaxed text-white/55">
|
||||
Seedance launches in ArtCraft ahead of anywhere else.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PricingSection>
|
||||
)}
|
||||
<PricingSection id="plans">
|
||||
<SectionLabel
|
||||
index="02"
|
||||
label="Choose your plan"
|
||||
annotation="Every paid plan includes video credits"
|
||||
/>
|
||||
<PricingTable
|
||||
showHeader={false}
|
||||
websiteLayout
|
||||
showSeedanceFeatures
|
||||
showEnterprise
|
||||
/>
|
||||
</PricingSection>
|
||||
<PricingSection id="credits">
|
||||
<div className="grid md:grid-cols-2">
|
||||
<div className="p-6 md:p-10">
|
||||
<p className="pricing-hud text-white/40">
|
||||
{isLoggedIn ? "Need more credits?" : "Already have an account?"}
|
||||
</p>
|
||||
<h2 className="mt-4 font-display text-2xl font-medium tracking-[-0.02em]">
|
||||
One-time credit packs
|
||||
</h2>
|
||||
<p className="mt-2 max-w-md leading-relaxed text-white/55">
|
||||
Top up without changing your plan. Credit packs never expire.
|
||||
</p>
|
||||
{isLoggedIn ? (
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="mt-6 gap-2 rounded-none text-[11px]"
|
||||
onClick={() => setCreditsModalOpen(true)}
|
||||
>
|
||||
<CoinsIcon aria-hidden className="h-4 w-4 text-primary" />
|
||||
Buy credits
|
||||
</Button>
|
||||
) : (
|
||||
<Link
|
||||
to="/login?from=%2Fpricing"
|
||||
className="pricing-hud mt-6 inline-flex border border-white/15 px-4 py-3 hover:bg-white/10"
|
||||
>
|
||||
Sign in to buy credits
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<div className="border-t border-white/15 p-6 md:border-l md:border-t-0 md:p-10">
|
||||
<p className="pricing-hud text-white/40">† Footnote</p>
|
||||
<p className="mt-4 max-w-md leading-relaxed text-white/55">
|
||||
ArtCraft can be used without paying for a subscription. You can
|
||||
bring your own compute and third-party subscriptions. We hope
|
||||
you’ll subscribe, though, as that helps accelerate our
|
||||
development.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</PricingSection>
|
||||
</main>
|
||||
{isLoggedIn && (
|
||||
<CreditsModal
|
||||
isOpen={creditsModalOpen}
|
||||
@@ -207,4 +201,40 @@ const Pricing = () => {
|
||||
);
|
||||
};
|
||||
|
||||
function PricingSection({
|
||||
id,
|
||||
children,
|
||||
}: {
|
||||
id?: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<section id={id} className="border-t border-white/15">
|
||||
<div className="relative mx-auto max-w-[1280px] border-x border-white/15">
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function SectionLabel({
|
||||
index,
|
||||
label,
|
||||
annotation,
|
||||
}: {
|
||||
index: string;
|
||||
label: string;
|
||||
annotation: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="pricing-hud flex items-center justify-between gap-4 border-b border-white/15 px-6 py-3 text-white/55 md:px-10">
|
||||
<p>
|
||||
<span className="text-white/40">{index} / </span>
|
||||
{label}
|
||||
</p>
|
||||
<p className="hidden text-white/40 sm:block">{annotation}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Pricing;
|
||||
@@ -145,7 +145,7 @@ export default function Referrals() {
|
||||
<span className="inline-block font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/60 mb-5">
|
||||
Share the craft
|
||||
</span>
|
||||
<h1 className="text-5xl tracking-[-0.035em] font-medium leading-[1.02] mb-5">
|
||||
<h1 className="font-display text-3xl sm:text-4xl tracking-tight leading-[1.02] mb-5">
|
||||
Referrals
|
||||
</h1>
|
||||
<p className="mx-auto text-base sm:text-lg text-white/55 leading-relaxed">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AUTH_LABEL_CLASSES, AUTH_PASSWORD_INPUT_CLASSES, AUTH_FORM_PADDING } from "../../components/auth/auth-form-styles";
|
||||
import { EyeIcon, EyeOffIcon, LoaderCircleIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
@@ -6,6 +7,8 @@ import { useState } from "react";
|
||||
import { useNavigate, Navigate } from "react-router-dom";
|
||||
import { UsersApi } from "@storyteller/api";
|
||||
import { useSession, refreshSession } from "../../lib/session";
|
||||
import { AuthHeader } from "../../components/auth/auth-layout";
|
||||
import { AuthPageFrame } from "../../components/auth/auth-page-frame";
|
||||
import Seo from "../../components/seo";
|
||||
import { toast } from "../../components/toast/toast";
|
||||
|
||||
@@ -66,26 +69,15 @@ const SetPassword = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative min-h-screen bg-ui-background text-white overflow-hidden flex flex-col">
|
||||
<AuthPageFrame>
|
||||
<Seo
|
||||
title="Set Password - ArtCraft"
|
||||
description="Set a password for your ArtCraft account."
|
||||
/>
|
||||
|
||||
<main className="relative z-10 flex-1 flex items-center justify-center p-4">
|
||||
<div className="w-full max-w-md bg-[#101014] border border-white/15 p-6 py-8">
|
||||
<div className="text-center mb-8">
|
||||
<img
|
||||
src="/images/artcraft-icon.png"
|
||||
alt="ArtCraft"
|
||||
className="mx-auto mb-6 h-12 w-auto select-none pointer-events-none"
|
||||
draggable={false}
|
||||
/>
|
||||
<h1 className="text-2xl font-semibold mb-2">Set a password</h1>
|
||||
<p className="text-white/60 text-sm">
|
||||
Create a password so you can also log into the desktop app
|
||||
</p>
|
||||
</div>
|
||||
<main className={AUTH_FORM_PADDING}>
|
||||
<div className="w-full">
|
||||
<AuthHeader title={<>Set your <span className="font-serif-italic">password.</span></>} subtitle="Create a password to sign in on the web and desktop." />
|
||||
|
||||
<form
|
||||
className="space-y-4"
|
||||
@@ -101,7 +93,7 @@ const SetPassword = () => {
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/60 ml-1">
|
||||
<label className={AUTH_LABEL_CLASSES}>
|
||||
Password
|
||||
</label>
|
||||
<div className="relative">
|
||||
@@ -111,7 +103,7 @@ const SetPassword = () => {
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Min. 8 characters"
|
||||
autoFocus
|
||||
inputClassName="w-full bg-ui-controls border border-white/15 focus:border-white/40 px-4 py-3 text-white placeholder-white/20 outline-none transition-colors pr-12"
|
||||
inputClassName={AUTH_PASSWORD_INPUT_CLASSES}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@@ -125,7 +117,7 @@ const SetPassword = () => {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/60 ml-1">
|
||||
<label className={AUTH_LABEL_CLASSES}>
|
||||
Confirm password
|
||||
</label>
|
||||
<div className="relative">
|
||||
@@ -134,7 +126,7 @@ const SetPassword = () => {
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
placeholder="Re-enter your password"
|
||||
inputClassName="w-full bg-ui-controls border border-white/15 focus:border-white/40 px-4 py-3 text-white placeholder-white/20 outline-none transition-colors pr-12"
|
||||
inputClassName={AUTH_PASSWORD_INPUT_CLASSES}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@@ -165,11 +157,7 @@ const SetPassword = () => {
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div className="relative z-10 py-6 text-center text-white/20 text-xs">
|
||||
© {new Date().getFullYear()} ArtCraft. All rights reserved.
|
||||
</div>
|
||||
</div>
|
||||
</AuthPageFrame>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { AuthHeader, AuthFooter, SignupForm } from "../../components/auth";
|
||||
import Seo from "../../components/seo";
|
||||
import { Reveal } from "../../components/motion/reveal";
|
||||
|
||||
const Signup = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -12,17 +11,13 @@ const Signup = () => {
|
||||
title="Sign Up - ArtCraft"
|
||||
description="Create your ArtCraft account."
|
||||
/>
|
||||
<AuthHeader title="Create an Account" subtitle="Join thousands of creators" />
|
||||
|
||||
{/* SignupForm runs its own field cascade internally (see signup-form.tsx).
|
||||
The footer picks up the tail of that cascade — the form's six staggered
|
||||
steps land by ~0.36s, so the footer follows at ~0.44s. */}
|
||||
<AuthHeader title={<>Make it <span className="font-serif-italic">yours.</span></>} subtitle="Create your account and start crafting." />
|
||||
<SignupForm
|
||||
onSuccess={() => navigate("/welcome")}
|
||||
signupSource="artcraft"
|
||||
/>
|
||||
|
||||
<Reveal inView={false} delay={0.44}>
|
||||
<div>
|
||||
<AuthFooter>
|
||||
Already have an account?{" "}
|
||||
<Link
|
||||
@@ -32,7 +27,7 @@ const Signup = () => {
|
||||
Log in
|
||||
</Link>
|
||||
</AuthFooter>
|
||||
</Reveal>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -44,12 +44,12 @@ const Support = () => {
|
||||
"Get help with ArtCraft: browse the FAQ, join our Discord community, or email us directly.";
|
||||
|
||||
return (
|
||||
<div className="relative min-h-screen bg-ui-background text-white">
|
||||
<div className="relative min-h-full bg-ui-background text-white">
|
||||
<Seo title={title} description={description} />
|
||||
|
||||
<main className="relative z-10 mx-auto w-full max-w-4xl px-4 sm:px-6 pt-24 sm:pt-32 pb-20">
|
||||
<header className="mb-8 sm:mb-10 flex flex-col items-center text-center gap-3">
|
||||
<h1 className="text-3xl sm:text-5xl font-semibold tracking-[-0.02em]">
|
||||
<main className="relative z-10 mx-auto w-full max-w-4xl px-5 py-8 sm:px-8 sm:py-12">
|
||||
<header className="mb-8 flex flex-col gap-3 border-b border-ui-border pb-8">
|
||||
<h1 className="font-display text-3xl sm:text-4xl tracking-tight">
|
||||
Support
|
||||
</h1>
|
||||
<p className="text-[15px] sm:text-base text-white/60 max-w-xl">
|
||||
|
||||
@@ -7,21 +7,22 @@
|
||||
/* Sidebar tokens (consumed by tailwind colors in tailwind.config.js).
|
||||
Brutalist system: near-black surfaces, white/15 hairlines, white
|
||||
accent for selection/focus. */
|
||||
color-scheme: dark;
|
||||
--sidebar-bg: #1e1f22;
|
||||
--sidebar-fg: rgba(255, 255, 255, 0.72);
|
||||
--sidebar-border: rgba(255, 255, 255, 0.15);
|
||||
--sidebar-accent: rgba(255, 255, 255, 0.08);
|
||||
--sidebar-accent-fg: #ffffff;
|
||||
--sidebar-ring: rgba(255, 255, 255, 0.4);
|
||||
--sidebar-ring: #74aaff;
|
||||
|
||||
/* shadcn design tokens consumed by @storyteller/ui-video-editor.
|
||||
Dark-theme palette (the editor only runs in dark mode today).
|
||||
--primary matches the webapp's brand blue (#2d81ff). */
|
||||
--background: 240 5% 8%;
|
||||
--background: 225 10% 8%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 240 11% 7%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 240 4% 5%;
|
||||
--popover: 240 11% 7%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--primary: 216 100% 59%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
@@ -35,7 +36,7 @@
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 100% / 0.15;
|
||||
--input: 0 0% 100% / 0.15;
|
||||
--ring: 0 0% 100% / 0.6;
|
||||
--ring: 217 100% 73%;
|
||||
|
||||
/* ── Motion tokens ──────────────────────────────────────────────────
|
||||
Shared easing curves + durations so motion across the app feels like
|
||||
@@ -144,6 +145,7 @@
|
||||
h1,
|
||||
h2 {
|
||||
font-stretch: 118%;
|
||||
font-weight: 620;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -156,12 +158,22 @@
|
||||
"Helvetica Neue",
|
||||
Arial,
|
||||
sans-serif;
|
||||
color: #ffffff;
|
||||
background-color: #0b0b0c;
|
||||
color: #f2f1ee;
|
||||
background-color: #121316;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: #2d81ff;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: 2px solid #74aaff;
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
/* Mono HUD micro-label, matching the marketing site's .hud-label. */
|
||||
.hud-label {
|
||||
font-family:
|
||||
@@ -514,3 +526,26 @@
|
||||
vertical-align: -0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fine registration marks echo the landing page section corners. */
|
||||
.frame-corner-mark {
|
||||
position: absolute;
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
margin: -5px;
|
||||
pointer-events: none;
|
||||
background:
|
||||
linear-gradient(rgba(242, 241, 238, 0.4), rgba(242, 241, 238, 0.4)) center / 1px 11px no-repeat,
|
||||
linear-gradient(rgba(242, 241, 238, 0.4), rgba(242, 241, 238, 0.4)) center / 11px 1px no-repeat;
|
||||
}
|
||||
|
||||
/* Contained section borders match the landing without decorative extensions. */
|
||||
.create-empty-section {
|
||||
margin-inline: 12px;
|
||||
border: 1px solid rgba(242, 241, 238, 0.2);
|
||||
background: rgba(242, 241, 238, 0.015);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.create-empty-section { margin-inline: 32px; }
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
const { createGlobPatternsForDependencies } = require("@nx/react/tailwind");
|
||||
const { join } = require("path");
|
||||
import colors, { teal } from "tailwindcss/colors";
|
||||
import colors from "tailwindcss/colors";
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
@@ -167,7 +167,7 @@ module.exports = {
|
||||
900: "#3d3d3d",
|
||||
950: "#3E3E41",
|
||||
},
|
||||
"base-fg": "rgb(255 255 255 / <alpha-value>)",
|
||||
"base-fg": "rgb(242 241 238 / <alpha-value>)",
|
||||
sidebar: {
|
||||
DEFAULT: "var(--sidebar-bg)",
|
||||
foreground: "var(--sidebar-fg)",
|
||||
@@ -180,17 +180,18 @@ module.exports = {
|
||||
// Brutalist system: near-black surfaces with white/15 hairline
|
||||
// borders, aligned with artcraft-website + artcraft-website-next.
|
||||
background: "#121316",
|
||||
panel: "#0b0b0c",
|
||||
panel: "#101014",
|
||||
"panel-border": "rgb(255 255 255 / 0.15)",
|
||||
border: "rgb(255 255 255 / 0.15)",
|
||||
// Raised control surface — a step above the #0b0b0c page/panel bg
|
||||
// (matches the sidebar's #1e1f22) so buttons and inputs read as
|
||||
// surfaces instead of blending into the background.
|
||||
// Raised controls stay distinct from the website-matched panel surface.
|
||||
controls: "#1e1f22",
|
||||
"controls-button": "#1e1f22",
|
||||
"controls-border": "rgb(255 255 255 / 0.15)",
|
||||
divider: "rgb(255 255 255 / 0.15)",
|
||||
modal: "#0b0b0c",
|
||||
modal: "#101014",
|
||||
ink: "#f2f1ee",
|
||||
"accent-ink": "#74aaff",
|
||||
sunken: "#060607",
|
||||
},
|
||||
"axis-x": "#D33242",
|
||||
"axis-y": "#308752",
|
||||
|
||||
@@ -68,8 +68,9 @@ Then read values where they're used:
|
||||
The panel picks up new groups automatically — no panel changes needed.
|
||||
Registered groups: Intro (page-intro choreography beats; `src/lib/intro.ts`),
|
||||
Wordmark (logo-A optics, blade tuck, contrast scrim;
|
||||
`src/components/landing/hero-wordmark.tsx`), Galaxy layout, Galaxy motion,
|
||||
Galaxy pointer, Galaxy look (hero galaxy;
|
||||
`src/components/landing/hero-wordmark.tsx`), Galaxy layout, Galaxy mobile
|
||||
(small-screen spiral profile — replaces the layout dials below its area
|
||||
threshold), Galaxy motion, Galaxy pointer, Galaxy look (hero galaxy;
|
||||
`src/components/landing/hero-galaxy-tunables.ts`) and Ruler layout, Ruler
|
||||
motion, Ruler look (scroll ruler;
|
||||
`src/components/ruler/ruler-tunables.ts`).
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
# Base directory: frontend/apps/artcraft-website-next
|
||||
# (this app is standalone — its own package-lock.json, not the Nx workspace)
|
||||
# Manage the build command and publish directory here; leave those UI fields empty.
|
||||
# The Next.js runtime handles routing, the /cdn-media and /ext-media proxy
|
||||
# rewrites, and the legacy-route redirects declared in next.config.ts.
|
||||
# The Next.js runtime handles routing, the /cdn-media proxy rewrite, and the
|
||||
# legacy-route redirects declared in next.config.ts. Other media is in public/.
|
||||
|
||||
[build]
|
||||
command = "npm run build"
|
||||
|
||||
@@ -1,19 +1,5 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
// Marketing routes the nav/footer reference but this app doesn't serve yet.
|
||||
// They bounce to the currently shipping site (same dependency as MEDIA_BASE —
|
||||
// this app already assumes the Vite site stays live) so no link dead-ends.
|
||||
// Delete a route from this list when its page migrates here.
|
||||
const LEGACY_ROUTES = [
|
||||
"download",
|
||||
"pricing",
|
||||
"tutorials",
|
||||
"news",
|
||||
"faq",
|
||||
"press-kit",
|
||||
"support",
|
||||
];
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
reactStrictMode: true,
|
||||
// Pin the project root: the repo has lockfiles both here and in the Nx
|
||||
@@ -22,16 +8,21 @@ const nextConfig: NextConfig = {
|
||||
turbopack: { root: __dirname },
|
||||
outputFileTracingRoot: __dirname,
|
||||
async redirects() {
|
||||
return LEGACY_ROUTES.map((route) => ({
|
||||
source: `/${route}`,
|
||||
destination: `https://getartcraft.com/${route}`,
|
||||
permanent: false,
|
||||
}));
|
||||
// Legacy Stripe return paths from the Vite site; checkout now lives in
|
||||
// the webapp, which serves the canonical slash forms.
|
||||
return ["checkout_success", "checkout_cancel", "portal_closed"].map(
|
||||
(route) => ({
|
||||
source: `/${route}`,
|
||||
destination: `https://app.getartcraft.com/checkout/${
|
||||
route === "checkout_success" ? "success" : "cancel"
|
||||
}`,
|
||||
permanent: false,
|
||||
}),
|
||||
);
|
||||
},
|
||||
// Same-origin proxies for large media served from other origins. The hero
|
||||
// wall draws its clips as WebGL video textures, which need CORS-clean
|
||||
// sources, and neither CDN sends Access-Control-Allow-Origin, so those
|
||||
// requests route through our own origin.
|
||||
// The hero wall uses WebGL video textures, which need CORS-clean sources.
|
||||
// The showcase CDN sends no Access-Control-Allow-Origin header, so these
|
||||
// clips use a same-origin proxy. Other marketing media lives in public/.
|
||||
async rewrites() {
|
||||
return [
|
||||
// Showcase clips for the hero wall (see cdnMediaUrl in lib/links.ts).
|
||||
@@ -39,11 +30,6 @@ const nextConfig: NextConfig = {
|
||||
source: "/cdn-media/:path*",
|
||||
destination: "https://frontend-cdn.fakeyou.com/:path*",
|
||||
},
|
||||
// Feature footage and other assets still hosted on the live site.
|
||||
{
|
||||
source: "/ext-media/:path*",
|
||||
destination: "https://getartcraft.com/:path*",
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"gsap": "3.15.0",
|
||||
"lenis": "1.3.23",
|
||||
"lucide-react": "^1.31.0",
|
||||
"next": "16.2.6",
|
||||
"next": "^16.2.12",
|
||||
"react": "19.2.6",
|
||||
"react-dom": "19.2.6",
|
||||
"tailwind-merge": "^3.6.0",
|
||||
@@ -608,15 +608,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.6.tgz",
|
||||
"integrity": "sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==",
|
||||
"version": "16.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.12.tgz",
|
||||
"integrity": "sha512-d0Z5Bc13Fa4nR8pFAKx2jay2yhJM16vlfHbTzYnUQAxlNb6B6lmn4hjt69lYNt4kRtyYP6gEM49lPRHNbIyneg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@next/swc-darwin-arm64": {
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.6.tgz",
|
||||
"integrity": "sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==",
|
||||
"version": "16.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.12.tgz",
|
||||
"integrity": "sha512-0W1R0teHWJrqKX0FH20IzzIWAOuGtBxPGuObrxy1lE8hQvCFj49KE8a3WUg0D7sq6rn6zkM4c7YGUnhudBS6oA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -630,9 +630,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-x64": {
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.6.tgz",
|
||||
"integrity": "sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==",
|
||||
"version": "16.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.12.tgz",
|
||||
"integrity": "sha512-Hy5Ls099+aFUmOLmIgPfLqNi6iCwhL3uQCssz5rWk+5Nkc6TUKCE83DY5BbNylfm3+mfwcSFnLRfrZDJhVxdtw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -646,12 +646,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.6.tgz",
|
||||
"integrity": "sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==",
|
||||
"version": "16.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.12.tgz",
|
||||
"integrity": "sha512-+YqU2h1cQkHsGfvjAsrSmst8UIFBibBGm5x3Xgel8NLMiDQtNOM4sM2GOEMvG5YiOBNeN/Ykk8cQC2S0Xrqljg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -662,12 +665,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-musl": {
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.6.tgz",
|
||||
"integrity": "sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==",
|
||||
"version": "16.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.12.tgz",
|
||||
"integrity": "sha512-0qjhiYBaKAqF63LA1ZWAAnKTzFUguAaZiRa5etMLGGPj/B6uEVjtIZldIzFEp3wHlB0koK6aTzqPtSdplTCjoA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -678,12 +684,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-gnu": {
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.6.tgz",
|
||||
"integrity": "sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==",
|
||||
"version": "16.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.12.tgz",
|
||||
"integrity": "sha512-7A3q26W+h7gnA15uqBToNuDqBEFZZcqh0mW2mn4AJh/G5pdg2RVE3n4slzLEliASZFG3NmsbEzng/x2Sh09mBg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -694,12 +703,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-musl": {
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.6.tgz",
|
||||
"integrity": "sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==",
|
||||
"version": "16.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.12.tgz",
|
||||
"integrity": "sha512-qSjL/uppm+cbh21s72Ss8gkiOhQ4dExWHNGOWy6eZV7STj5WsKehgxT61beSsOj+YYQuTplL376lOCdMQU5T8w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -710,9 +722,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.6.tgz",
|
||||
"integrity": "sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==",
|
||||
"version": "16.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.12.tgz",
|
||||
"integrity": "sha512-X6hzsOUJac/e7AWSbn9gQ9nzHld1xWP5iyjHpYWvud8pufB679O1xg4JDyKr8Xd69Jvd+kM2Der6uftiZCmjYA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -726,9 +738,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-x64-msvc": {
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.6.tgz",
|
||||
"integrity": "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==",
|
||||
"version": "16.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.12.tgz",
|
||||
"integrity": "sha512-F6fakeHuFTLOPt0bslQJdf+xtT+WIP9DVn/m4y1w1mRnVPyh3D/cNvzlRkxM444xfm+IvvYNSOrKiA2CDJ0Uxw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2209,12 +2221,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-16.2.6.tgz",
|
||||
"integrity": "sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==",
|
||||
"version": "16.2.12",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-16.2.12.tgz",
|
||||
"integrity": "sha512-iD59eYQWmbFcEbX7v/acG5DRym9iw1DdaPoD0WTA920naWsE25wShzJW4+UvAs8MK9EC2kBfIH6vtto1H1PHGw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@next/env": "16.2.6",
|
||||
"@next/env": "16.2.12",
|
||||
"@swc/helpers": "0.5.15",
|
||||
"baseline-browser-mapping": "^2.9.19",
|
||||
"caniuse-lite": "^1.0.30001579",
|
||||
@@ -2228,14 +2240,14 @@
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@next/swc-darwin-arm64": "16.2.6",
|
||||
"@next/swc-darwin-x64": "16.2.6",
|
||||
"@next/swc-linux-arm64-gnu": "16.2.6",
|
||||
"@next/swc-linux-arm64-musl": "16.2.6",
|
||||
"@next/swc-linux-x64-gnu": "16.2.6",
|
||||
"@next/swc-linux-x64-musl": "16.2.6",
|
||||
"@next/swc-win32-arm64-msvc": "16.2.6",
|
||||
"@next/swc-win32-x64-msvc": "16.2.6",
|
||||
"@next/swc-darwin-arm64": "16.2.12",
|
||||
"@next/swc-darwin-x64": "16.2.12",
|
||||
"@next/swc-linux-arm64-gnu": "16.2.12",
|
||||
"@next/swc-linux-arm64-musl": "16.2.12",
|
||||
"@next/swc-linux-x64-gnu": "16.2.12",
|
||||
"@next/swc-linux-x64-musl": "16.2.12",
|
||||
"@next/swc-win32-arm64-msvc": "16.2.12",
|
||||
"@next/swc-win32-x64-msvc": "16.2.12",
|
||||
"sharp": "^0.34.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"gsap": "3.15.0",
|
||||
"lenis": "1.3.23",
|
||||
"lucide-react": "^1.31.0",
|
||||
"next": "16.2.6",
|
||||
"next": "^16.2.12",
|
||||
"react": "19.2.6",
|
||||
"react-dom": "19.2.6",
|
||||
"tailwind-merge": "^3.6.0",
|
||||
|
||||
|
After Width: | Height: | Size: 463 KiB |
|
After Width: | Height: | Size: 181 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 19 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 20 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 22 MiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 16 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 22 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 374 KiB |
|
After Width: | Height: | Size: 216 KiB |
|
After Width: | Height: | Size: 167 KiB |
|
After Width: | Height: | Size: 199 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 4.2 MiB |
|
After Width: | Height: | Size: 7.4 MiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 48 KiB |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Qwen</title><path d="M12.604 1.34c.393.69.784 1.382 1.174 2.075a.18.18 0 00.157.091h5.552c.174 0 .322.11.446.327l1.454 2.57c.19.337.24.478.024.837-.26.43-.513.864-.76 1.3l-.367.658c-.106.196-.223.28-.04.512l2.652 4.637c.172.301.111.494-.043.77-.437.785-.882 1.564-1.335 2.34-.159.272-.352.375-.68.37-.777-.016-1.552-.01-2.327.016a.099.099 0 00-.081.05 575.097 575.097 0 01-2.705 4.74c-.169.293-.38.363-.725.364-.997.003-2.002.004-3.017.002a.537.537 0 01-.465-.271l-1.335-2.323a.09.09 0 00-.083-.049H4.982c-.285.03-.553-.001-.805-.092l-1.603-2.77a.543.543 0 01-.002-.54l1.207-2.12a.198.198 0 000-.197 550.951 550.951 0 01-1.875-3.272l-.79-1.395c-.16-.31-.173-.496.095-.965.465-.813.927-1.625 1.387-2.436.132-.234.304-.334.584-.335a338.3 338.3 0 012.589-.001.124.124 0 00.107-.063l2.806-4.895a.488.488 0 01.422-.246c.524-.001 1.053 0 1.583-.006L11.704 1c.341-.003.724.032.9.34zm-3.432.403a.06.06 0 00-.052.03L6.254 6.788a.157.157 0 01-.135.078H3.253c-.056 0-.07.025-.041.074l5.81 10.156c.025.042.013.062-.034.063l-2.795.015a.218.218 0 00-.2.116l-1.32 2.31c-.044.078-.021.118.068.118l5.716.008c.046 0 .08.02.104.061l1.403 2.454c.046.081.092.082.139 0l5.006-8.76.783-1.382a.055.055 0 01.096 0l1.424 2.53a.122.122 0 00.107.062l2.763-.02a.04.04 0 00.035-.02.041.041 0 000-.04l-2.9-5.086a.108.108 0 010-.113l.293-.507 1.12-1.977c.024-.041.012-.062-.035-.062H9.2c-.059 0-.073-.026-.043-.077l1.434-2.505a.107.107 0 000-.114L9.225 1.774a.06.06 0 00-.053-.031zm6.29 8.02c.046 0 .058.02.034.06l-.832 1.465-2.613 4.585a.056.056 0 01-.05.029.058.058 0 01-.05-.029L8.498 9.841c-.02-.034-.01-.052.028-.054l.216-.012 6.722-.012z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72.22 65.68">
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<path d="M69.38,45.46c-3.18-6.24-6.58-12.49-9.86-18.53-3.31-6.08-6.72-12.37-9.91-18.63l-.05-.09C46.44,3.07,41.24,0,35.66,0s-10.34,3-13.13,8.23L.97,48.96l-.03.07c-1.51,3.8-1.2,8.03.82,11.31,1.9,3.08,5.01,4.96,8.76,5.28.38.03.75.05,1.1.05,2.94,0,4.98-1.12,7.33-2.41,7.13-3.91,19.45-11.21,27.61-16.04,2.6-1.54,4.95-2.93,6.31-3.73l10.71,20.43c.2.39.54.7.95.86.46.19.98.2,1.43,0,1.39-.66,3.05-2.47,3.07-2.49,5.22-5.9,3.03-11.55.34-16.83ZM34.53,14.8c.31-.46.77-.99,1.32-.99.58,0,1.29.57,1.85,1.49,1.57,2.56,2.99,5.44,4.35,8.22,1.34,2.73,2.72,5.55,4.29,8.15l.02.05-29.6,16.99L34.53,14.8Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 781 B |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Flux</title><path d="M0 20.683L12.01 2.5 24 20.683h-2.233L12.009 5.878 3.471 18.806h12.122l1.239 1.877H0z"></path><path d="M8.069 16.724l2.073-3.115 2.074 3.115H8.069zM18.24 20.683l-5.668-8.707h2.177l5.686 8.707h-2.196zM19.74 11.676l2.13-3.19 2.13 3.19h-4.26z"></path></svg>
|
||||
|
After Width: | Height: | Size: 438 B |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>ByteDance</title><path d="M14.944 18.587l-1.704-.445V10.01l1.824-.462c1-.254 1.84-.461 1.88-.453.032 0 .056 2.235.056 4.972v4.973l-.176-.008c-.104 0-.952-.207-1.88-.446z"></path><path d="M7 16.542c0-2.736.024-4.98.064-4.98.032-.008.872.2 1.88.454l1.816.461-.016 4.05-.024 4.049-1.632.422c-.896.23-1.736.445-1.856.469L7 21.523v-4.98z"></path><path d="M19.24 12.477c0-9.03.008-9.515.144-9.475.072.024.784.207 1.576.406.792.207 1.576.405 1.744.445l.296.08-.016 8.56-.024 8.568-1.624.414c-.888.23-1.728.437-1.856.47l-.24.055v-9.523z"></path><path d="M1 12.509c0-4.678.024-8.505.064-8.505.032 0 .872.207 1.872.454l1.824.461v7.582c0 4.16-.016 7.574-.032 7.574-.024 0-.872.215-1.88.47L1 21.013v-8.505z"></path></svg>
|
||||
|
After Width: | Height: | Size: 873 B |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Fal</title><path clip-rule="evenodd" d="M15.477 0c.415 0 .749.338.788.752a7.775 7.775 0 006.985 6.984c.413.04.752.373.752.788v6.952c0 .415-.338.748-.752.788a7.775 7.775 0 00-6.985 6.984c-.04.414-.373.752-.788.752H8.525c-.416 0-.749-.338-.789-.752a7.775 7.775 0 00-6.984-6.984c-.414-.04-.752-.373-.752-.788V8.524c0-.415.338-.748.752-.788A7.775 7.775 0 007.736.752C7.776.338 8.11 0 8.526 0h6.95zM4.819 11.98a7.226 7.226 0 007.223 7.23 7.226 7.226 0 007.223-7.23c0-3.994-3.234-7.23-7.223-7.23a7.227 7.227 0 00-7.223 7.23z"></path></svg>
|
||||
|
After Width: | Height: | Size: 697 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M64 144C64 117.5 85.5 96 112 96L208 96C234.5 96 256 117.5 256 144L256 160L384 160L384 144C384 117.5 405.5 96 432 96L528 96C554.5 96 576 117.5 576 144L576 240C576 266.5 554.5 288 528 288L432 288C405.5 288 384 266.5 384 240L384 224L256 224L256 240C256 247.3 254.3 254.3 251.4 260.5L320 352L400 352C426.5 352 448 373.5 448 400L448 496C448 522.5 426.5 544 400 544L304 544C277.5 544 256 522.5 256 496L256 400C256 392.7 257.7 385.7 260.6 379.5L192 288L112 288C85.5 288 64 266.5 64 240L64 144z"/></svg>
|
||||
|
After Width: | Height: | Size: 717 B |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Gemini</title><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z"></path></svg>
|
||||
|
After Width: | Height: | Size: 651 B |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Grok</title><path d="M9.27 15.29l7.978-5.897c.391-.29.95-.177 1.137.272.98 2.369.542 5.215-1.41 7.169-1.951 1.954-4.667 2.382-7.149 1.406l-2.711 1.257c3.889 2.661 8.611 2.003 11.562-.953 2.341-2.344 3.066-5.539 2.388-8.42l.006.007c-.983-4.232.242-5.924 2.75-9.383.06-.082.12-.164.179-.248l-3.301 3.305v-.01L9.267 15.292M7.623 16.723c-2.792-2.67-2.31-6.801.071-9.184 1.761-1.763 4.647-2.483 7.166-1.425l2.705-1.25a7.808 7.808 0 00-1.829-1A8.975 8.975 0 005.984 5.83c-2.533 2.536-3.33 6.436-1.962 9.764 1.022 2.487-.653 4.246-2.34 6.022-.599.63-1.199 1.259-1.682 1.925l7.62-6.815"></path></svg>
|
||||
|
After Width: | Height: | Size: 756 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
|
||||
<path d="M.03,2.19c.14.03.29.07.43.09,1.26.16,1.76-1.08,2.78-1.5,1.26-.52,2.77-.09,2.67,1.5s-1.89,2.3-2.88,3.2c-.38.34-.74.91-.36,1.4.5.64,1.25.06,1.65-.36,1.18-1.22,2.38-3.76,4.38-3.57,1.47.14,2.2,1.65,2.32,2.97h.96l.03.03v1.36h-.99c-.09,1.37-.54,3.03-1.78,3.79-.9.55-2.02.51-2.73-.31-.78-.89-.43-1.96.16-2.84.71-1.06,1.85-1.83,3.12-2.04.04-.61-.34-1.2-.94-1.35-1.15-.3-2.03.74-2.65,1.54-.79,1-1.87,2.88-3.33,2.8-1.19-.07-1.99-1.08-1.93-2.24.05-1.04,1.04-1.89,1.83-2.46.52-.38,1.71-1.05,1.81-1.72.06-.41-.18-.75-.61-.65-.59.13-1.03.85-1.49,1.22-.69.56-1.57.85-2.43.48v-1.3ZM9.73,7.28c-.28.07-.56.16-.81.3-.58.31-1.35,1.09-1.45,1.77-.08.58.45.87.95.62.87-.43,1.26-1.79,1.31-2.69Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 824 B |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Kling</title><path clip-rule="evenodd" d="M5.493 21.234c-1.112-1.451-1.109-4.263-.081-7.459l-4.557-2.63a1.683 1.683 0 01-.85-1.304 1.505 1.505 0 01.08-.622 13.18 13.18 0 011.037-2.255c3.476-6.02 10.916-8.23 16.619-4.938.46.266.82.67 1.081 1.184.785 1.545.685 4.096-.234 6.954l4.557 2.631c.339.196.596.492.736.832a1.53 1.53 0 01.034 1.093 13.146 13.146 0 01-1.037 2.255c-3.476 6.02-10.916 8.23-16.619 4.938a2.6 2.6 0 01-.766-.68zm11.096-6.615c-2.073 3.591-5.808 5.316-8.343 3.852-1.267-.731-1.994-2.122-2.145-3.778-.095-1.035.036-2.173.4-3.32.217-.684.517-1.37.902-2.039l.008-.014c2.073-3.59 5.808-5.315 8.343-3.852.633.366 1.13.895 1.49 1.54.986 1.772.922 4.415-.285 6.914-.111.23-.232.457-.362.683l-.008.014z"></path></svg>
|
||||
|
After Width: | Height: | Size: 888 B |
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #010101;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path d="M9.26.37c.36.29.62.65.72,1.11.01.14.01.27.01.41t0,.06c0,.51-.07.99-.22,1.47t-.02.06c-.24.83-.68,1.56-1.25,2.21-.02.02-.04.05-.06.07-.21.23-.42.43-.66.63-.02.02-.04.03-.06.05-.87.72-1.93,1.12-3.04,1.29-.03,0-.06.01-.09.02-.63.1-1.3.08-1.85-.27-.11-.08-.21-.18-.31-.28-.02-.02-.03-.03-.05-.05-.32-.34-.43-.82-.43-1.27.02-.45.27-.87.59-1.17.36-.31.81-.47,1.29-.48.62-.01,1.16-.23,1.64-.62.02-.02.04-.03.07-.05.39-.33.67-.89.73-1.39,0-.11.02-.22.02-.33.03-.51.2-.93.57-1.29.07-.06.15-.12.23-.18.02-.01.03-.02.05-.04.63-.43,1.49-.36,2.09.06Z"/>
|
||||
<path d="M8.61,6.51c.06,0,.1.02.14.06t.04.05s.03.04.04.06c.01.02.03.04.04.06.01.02.03.04.04.06.4.57.68,1.19.88,1.85t.02.05c.22.71.32,1.62-.02,2.29-.24.47-.63.72-1.11.91-.48.14-1.02.06-1.46-.17-.47-.28-.74-.67-.89-1.19-.03-.12-.04-.25-.04-.37-.02-.65-.25-1.22-.71-1.68-.06-.05-.12-.11-.19-.16-.03-.03-.05-.05-.08-.08.13-.04.25-.08.38-.12.99-.29,2.17-.79,2.86-1.61l.04-.03Z"/>
|
||||
<path class="cls-1" d="M4.96.4c.34.27.63.67.69,1.1.02.29.02.58-.08.85t-.03.08c-.16.44-.5.77-.92.97-.29.13-.53.17-.85.18-.68.04-1.21.26-1.72.69-.02.02-.05.04-.07.06,0-.36,0-.71,0-1.07,0-.17,0-.33,0-.5,0-.16,0-.32,0-.48,0-.06,0-.12,0-.18,0-.63.12-1.11.56-1.58.67-.65,1.69-.66,2.43-.13Z"/>
|
||||
<path d="M1.98,7.67s.07.05.1.07c.02.01.04.03.06.04.04.03.09.07.13.1.41.3.88.5,1.39.51.58.01,1.1.17,1.52.59.35.4.5.86.47,1.39-.05.49-.31.89-.67,1.21-.46.35-1.01.43-1.57.36-.47-.1-.88-.4-1.15-.79-.07-.12-.13-.23-.18-.36,0-.02-.02-.05-.03-.07-.1-.31-.09-.61-.09-.93,0-.06,0-.12,0-.18,0-.16,0-.32,0-.48,0-.16,0-.33,0-.49,0-.32,0-.64,0-.96Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Midjourney</title><path d="M22.369 17.676c-1.387 1.259-3.17 2.378-5.332 3.417.044.03.086.057.13.083l.018.01.019.012c.216.123.42.184.641.184.222 0 .426-.061.642-.184l.018-.011.019-.011c.14-.084.266-.178.492-.366l.178-.148c.279-.232.426-.342.625-.456.304-.174.612-.266.949-.266.337 0 .645.092.949.266l.023.014c.188.109.334.219.602.442l.178.148c.221.184.346.278.483.36l.028.017.018.01c.21.12.407.181.62.185h.022a.31.31 0 110 .618c-.337 0-.645-.092-.95-.266a3.137 3.137 0 01-.09-.054l-.022-.014-.022-.013-.02-.014a5.356 5.356 0 01-.49-.377l-.159-.132a3.836 3.836 0 00-.483-.36l-.027-.017-.019-.01a1.256 1.256 0 00-.641-.185c-.222 0-.426.061-.641.184l-.02.011-.018.011c-.14.084-.266.178-.492.366l-.158.132a5.125 5.125 0 01-.51.39l-.022.014-.022.014-.09.054a1.868 1.868 0 01-.95.266c-.337 0-.644-.092-.949-.266a3.137 3.137 0 01-.09-.054l-.022-.014-.022-.013-.026-.017a4.881 4.881 0 01-.425-.325.308.308 0 01-.12-.1l-.098-.081a3.836 3.836 0 00-.483-.36l-.027-.017-.019-.01a1.256 1.256 0 00-.641-.185c-.222 0-.426.061-.642.184l-.018.011-.019.011c-.14.084-.266.178-.492.366l-.158.132a5.125 5.125 0 01-.51.39l-.023.014-.022.014-.09.054A1.868 1.868 0 0112 22c-.337 0-.645-.092-.949-.266a3.137 3.137 0 01-.09-.054l-.022-.014-.022-.013-.021-.014a5.356 5.356 0 01-.49-.377l-.158-.132a3.836 3.836 0 00-.483-.36l-.028-.017-.018-.01a1.256 1.256 0 00-.642-.185c-.221 0-.425.061-.641.184l-.019.011-.018.011c-.141.084-.266.178-.492.366l-.158.132a5.125 5.125 0 01-.511.39l-.022.014-.022.014-.09.054a1.868 1.868 0 01-.986.264c-.746-.09-1.319-.38-1.89-.866l-.035-.03c-.047-.041-.118-.106-.192-.174l-.196-.181-.107-.1-.011-.01a1.531 1.531 0 00-.336-.253.313.313 0 00-.095-.03h-.005c-.119.022-.238.059-.361.11a.308.308 0 01-.077.061l-.008.005a.309.309 0 01-.126.034 5.66 5.66 0 00-.774.518l-.416.324-.055.043a6.542 6.542 0 01-.324.236c-.305.207-.552.315-.8.315a.31.31 0 01-.01-.618h.01c.09 0 .235-.062.438-.198l.04-.027c.077-.054.163-.117.27-.199l.385-.301.06-.047c.268-.206.506-.373.73-.505l-.633-1.21a.309.309 0 01.254-.451l20.287-1.305a.309.309 0 01.228.537zm-1.118.14L2.369 19.03l.423.809c.128-.045.256-.078.388-.1a.31.31 0 01.052-.005c.132 0 .26.032.386.093.153.073.294.179.483.35l.016.015.092.086.144.134.097.089c.065.06.125.114.16.144.485.418.948.658 1.554.736h.011a1.25 1.25 0 00.6-.172l.021-.011.019-.011.018-.011c.141-.084.266-.178.492-.366l.178-.148c.279-.232.426-.342.625-.456.305-.174.612-.266.95-.266.336 0 .644.092.948.266l.023.014c.188.109.335.219.603.442l.177.148c.222.184.346.278.484.36l.027.017.019.01c.215.124.42.185.641.185.222 0 .426-.061.641-.184l.019-.011.018-.011c.141-.084.267-.178.493-.366l.177-.148c.28-.232.427-.342.626-.456.304-.174.612-.266.949-.266.337 0 .644.092.949.266l.025.015c.187.109.334.22.603.443 1.867-.878 3.448-1.811 4.73-2.832l.02-.016zM3.653 2.026C6.073 3.06 8.69 4.941 10.8 7.258c2.46 2.7 4.109 5.828 4.637 9.149a.31.31 0 01-.421.335c-2.348-.945-4.54-1.258-6.59-1.02-1.739.2-3.337.792-4.816 1.703-.294.182-.62-.182-.405-.454 1.856-2.355 2.581-4.99 2.343-7.794-.195-2.292-1.031-4.61-2.284-6.709a.31.31 0 01.388-.442zM10.04 4.45c1.778.543 3.892 2.102 5.782 4.243 1.984 2.248 3.552 4.934 4.347 7.582a.31.31 0 01-.401.38l-.022-.01-.386-.154a10.594 10.594 0 00-.291-.112l-.016-.006c-.68-.247-1.199-.291-1.944-.101a.31.31 0 01-.375-.218C15.378 11.123 13.073 7.276 9.775 5c-.291-.201-.072-.653.266-.55zM4.273 2.996l.008.015c1.028 1.94 1.708 4.031 1.885 6.113.213 2.513-.31 4.906-1.673 7.092l-.02.031.003-.001c1.198-.581 2.47-.969 3.825-1.132l.055-.006c1.981-.23 4.083.029 6.309.837l.066.025-.007-.039c-.593-2.95-2.108-5.737-4.31-8.179l-.07-.078c-1.785-1.96-3.944-3.6-6.014-4.65l-.057-.028zm7.92 3.238l.048.048c2.237 2.295 3.885 5.431 4.974 9.191l.038.132.022-.004c.71-.133 1.284-.063 1.963.18l.027.01.066.024.046.018-.025-.073c-.811-2.307-2.208-4.62-3.936-6.594l-.058-.065c-1.02-1.155-2.103-2.132-3.15-2.856l-.015-.011z"></path></svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Minimax</title><path d="M16.278 2c1.156 0 2.093.927 2.093 2.07v12.501a.74.74 0 00.744.709.74.74 0 00.743-.709V9.099a2.06 2.06 0 012.071-2.049A2.06 2.06 0 0124 9.1v6.561a.649.649 0 01-.652.645.649.649 0 01-.653-.645V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v7.472a2.037 2.037 0 01-2.048 2.026 2.037 2.037 0 01-2.048-2.026v-12.5a.785.785 0 00-.788-.753.785.785 0 00-.789.752l-.001 15.904A2.037 2.037 0 0113.441 22a2.037 2.037 0 01-2.048-2.026V18.04c0-.356.292-.645.652-.645.36 0 .652.289.652.645v1.934c0 .263.142.506.372.638.23.131.514.131.744 0a.734.734 0 00.372-.638V4.07c0-1.143.937-2.07 2.093-2.07zm-5.674 0c1.156 0 2.093.927 2.093 2.07v11.523a.648.648 0 01-.652.645.648.648 0 01-.652-.645V4.07a.785.785 0 00-.789-.78.785.785 0 00-.789.78v14.013a2.06 2.06 0 01-2.07 2.048 2.06 2.06 0 01-2.071-2.048V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v3.8a2.06 2.06 0 01-2.071 2.049A2.06 2.06 0 010 12.9v-1.378c0-.357.292-.646.652-.646.36 0 .653.29.653.646V12.9c0 .418.343.757.766.757s.766-.339.766-.757V9.099a2.06 2.06 0 012.07-2.048 2.06 2.06 0 012.071 2.048v8.984c0 .419.343.758.767.758.423 0 .766-.339.766-.758V4.07c0-1.143.937-2.07 2.093-2.07z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>OpenAI</title><path d="M21.55 10.004a5.416 5.416 0 00-.478-4.501c-1.217-2.09-3.662-3.166-6.05-2.66A5.59 5.59 0 0010.831 1C8.39.995 6.224 2.546 5.473 4.838A5.553 5.553 0 001.76 7.496a5.487 5.487 0 00.691 6.5 5.416 5.416 0 00.477 4.502c1.217 2.09 3.662 3.165 6.05 2.66A5.586 5.586 0 0013.168 23c2.443.006 4.61-1.546 5.361-3.84a5.553 5.553 0 003.715-2.66 5.488 5.488 0 00-.693-6.497v.001zm-8.381 11.558a4.199 4.199 0 01-2.675-.954c.034-.018.093-.05.132-.074l4.44-2.53a.71.71 0 00.364-.623v-6.176l1.877 1.069c.02.01.033.029.036.05v5.115c-.003 2.274-1.87 4.118-4.174 4.123zM4.192 17.78a4.059 4.059 0 01-.498-2.763c.032.02.09.055.131.078l4.44 2.53c.225.13.504.13.73 0l5.42-3.088v2.138a.068.068 0 01-.027.057L9.9 19.288c-1.999 1.136-4.552.46-5.707-1.51h-.001zM3.023 8.216A4.15 4.15 0 015.198 6.41l-.002.151v5.06a.711.711 0 00.364.624l5.42 3.087-1.876 1.07a.067.067 0 01-.063.005l-4.489-2.559c-1.995-1.14-2.679-3.658-1.53-5.63h.001zm15.417 3.54l-5.42-3.088L14.896 7.6a.067.067 0 01.063-.006l4.489 2.557c1.998 1.14 2.683 3.662 1.529 5.633a4.163 4.163 0 01-2.174 1.807V12.38a.71.71 0 00-.363-.623zm1.867-2.773a6.04 6.04 0 00-.132-.078l-4.44-2.53a.731.731 0 00-.729 0l-5.42 3.088V7.325a.068.068 0 01.027-.057L14.1 4.713c2-1.137 4.555-.46 5.707 1.513.487.833.664 1.809.499 2.757h.001zm-11.741 3.81l-1.877-1.068a.065.065 0 01-.036-.051V6.559c.001-2.277 1.873-4.122 4.181-4.12.976 0 1.92.338 2.671.954-.034.018-.092.05-.131.073l-4.44 2.53a.71.71 0 00-.365.623l-.003 6.173v.002zm1.02-2.168L12 9.25l2.414 1.375v2.75L12 14.75l-2.415-1.375v-2.75z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill-rule: evenodd;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="cls-1" d="M8.6,2.63c.89,0,1.75.35,2.39.99.63.63.99,1.49,1,2.38,0,.66-.2,1.3-.56,1.84-.36.55-.87.98-1.47,1.24-.6.26-1.27.34-1.91.24-.65-.11-1.25-.4-1.73-.84-.59-.5-1.19-.98-1.79-1.47l.86-.69c.84.67,1.57,1.27,1.85,1.5.39.29.87.45,1.37.45,1.23,0,2.32-1.02,2.3-2.28,0-.42-.13-.84-.35-1.19s-.54-.65-.92-.84c-.38-.19-.8-.27-1.22-.23-.42.04-.82.19-1.17.44-1.82,1.51-3.71,2.97-5.56,4.44-.75.53-1.64,0-1.64-.87C.01,6.58.01,5.42.02,4.26c0-.88.89-1.41,1.65-.88l2.03,1.64-.84.68-1.75-1.38c0,1.12,0,2.24,0,3.36,1.75-1.36,3.49-2.74,5.2-4.15.62-.57,1.44-.89,2.29-.89h0Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 807 B |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Recraft</title><path d="M19.667 8.275c0-4.57-4.15-8.275-9.27-8.275-1.774 0-3.213 3.705-3.213 8.275 0 1.143.09 2.233.253 3.224H4.29L1 23h9.4v-6.447c5.117 0 9.266-3.707 9.266-8.275l.001-.002zm-9.27-6.76c.93 0 1.682 3.028 1.682 6.76 0 3.733-.752 6.76-1.681 6.76-.93 0-1.681-3.027-1.681-6.76 0-3.732.752-6.76 1.68-6.76z"></path><path d="M19.848 16.552h-9.44L14.028 23h9.438l-3.618-6.448z"></path></svg>
|
||||
|
After Width: | Height: | Size: 562 B |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Replicate</title><path d="M22 10.552v2.26h-7.932V22H11.54V10.552H22zM22 2v2.264H4.528V22H2V2h20zm0 4.276V8.54H9.296V22H6.768V6.276H22z"></path></svg>
|
||||
|
After Width: | Height: | Size: 313 B |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Suno</title><path d="M16.5 0C20.642 0 24 5.373 24 12h-9c0 6.627-3.358 12-7.5 12C3.358 24 0 18.627 0 12h9c0-6.627 3.358-12 7.5-12z"></path></svg>
|
||||
|
After Width: | Height: | Size: 308 B |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Hunyuan</title><path d="M12 0c6.627 0 12 5.373 12 12s-5.373 12-12 12S0 18.627 0 12 5.373 0 12 0zm1.652 1.123l-.01-.001c.533.097 1.023.233 1.41.404 6.084 2.683 7.396 9.214 1.601 14.338a3.781 3.781 0 01-5.337-.328 3.654 3.654 0 01-.884-3.044c-1.934.6-3.295 2.305-3.524 4.45-.204 1.912.324 4.044 2.056 5.634l.245.067C10.1 22.876 11.036 23 12 23c6.075 0 11-4.925 11-11 0-5.513-4.056-10.08-9.348-10.877zM2.748 6.21c-.178.269-.348.536-.51.803l-.235.394.078-.167A10.957 10.957 0 001 12c0 4.919 3.228 9.083 7.682 10.49l.214.065C3.523 18.528 2.84 14.149 6.47 8.68A2.234 2.234 0 102.748 6.21zm10.157-5.172c4.408 1.33 3.61 5.41 2.447 6.924-.86 1.117-2.922 1.46-3.708 2.238-.666.657-1.077 1.462-1.212 2.291A5.303 5.303 0 0112 12.258a5.672 5.672 0 001.404-11.169 10.51 10.51 0 00-.5-.052z"></path></svg>
|
||||
|
After Width: | Height: | Size: 954 B |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
|
||||
<path d="M7.51,3.04q.14,0,.27,0c.1,0,.2,0,.3,0,.1,0,.21,0,.31,0,.1,0,.21,0,.31,0,.26,0,.51,0,.77,0,.29.67.58,1.34.87,2.01.15.34.29.67.44,1.01.15.35.3.7.45,1.05.06.14.12.27.18.41.08.19.17.38.25.57q.04.09.08.17c.11.25.17.42.27.69h-2.36c-.45-1.18-.89-2.37-1.3-3.56-.31.75-.58,1.51-.85,2.27-.04.13-.09.25-.13.38-.11.3-.22.61-.32.92h-2.26c.29-.76.59-1.51.91-2.26.04-.1.09-.2.13-.31.09-.21.18-.43.27-.64.12-.27.23-.55.35-.82.11-.26.22-.53.34-.79.04-.1.08-.2.13-.3q.06-.14.12-.27c.03-.08.07-.16.1-.24.07-.15.12-.28.12-.28,0,0,.14,0,.26,0Z"/>
|
||||
<path d="M1.65,2.96h3.82v1.65h-1.74v4.43H1.65V2.96Z"/>
|
||||
<path d="M0,2.96h1.3v1.74H0v-1.74Z"/>
|
||||
<path d="M8.25,6.78h.17c.23.72.46,1.43.7,2.17h-1.56c.21-.73.45-1.45.7-2.17Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 854 B |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Vidu</title><path d="M20.607 2.247c-2.917-.966-5.426 1.084-6.011 2.96 0 0-2.105 6.76-3.002 9.58-.428 1.346-1.489 3.548-3.487 3.548-1.627 0-2.463-1.527-2.816-2.437L2.865 9.431c-.281-.681.013-2.04 1.14-2.447 1.204-.432 1.978.575 2.178 1.11l3.022 7.74c.72-.928 1.178-2.438 1.476-3.507l-1.984-5.21C7.756 4.686 5.267 3.58 2.962 4.43 1.095 5.118-.702 7.474.275 10.435l2.504 6.44c.38.976 1.881 4.163 5.275 4.163 4.073 0 5.601-3.473 6.449-6.218.424-1.373 2.749-8.797 2.749-8.797.338-1.109 1.71-1.428 2.568-1.148.605.196 1.698 1.031 1.345 2.325-.066.236-1.92 6.209-2.604 8.026-.357.948-1.262 3.006-3.324 2.72-.628 1.39-1.15 2.199-1.94 2.925 2.572 1.218 6.32-.009 7.898-4.776.586-1.773 2.644-8.166 2.644-8.166.598-1.963-.469-4.768-3.232-5.682z"></path></svg>
|
||||
|
After Width: | Height: | Size: 912 B |